<?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: Gueta Quant </title>
    <description>The latest articles on DEV Community by Gueta Quant  (@guetaquant).</description>
    <link>https://dev.to/guetaquant</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%2F4084951%2F5f236106-8f97-48b1-9a2b-d4f210066eb6.jpg</url>
      <title>DEV Community: Gueta Quant </title>
      <link>https://dev.to/guetaquant</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/guetaquant"/>
    <language>en</language>
    <item>
      <title>Monte Carlo Permutation Testing in Python: How Random Trade Order Falsifies Your Equity Curve</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Mon, 28 Sep 2026 06:18:04 +0000</pubDate>
      <link>https://dev.to/guetaquant/monte-carlo-permutation-testing-in-python-how-random-trade-order-falsifies-your-equity-curve-fh5</link>
      <guid>https://dev.to/guetaquant/monte-carlo-permutation-testing-in-python-how-random-trade-order-falsifies-your-equity-curve-fh5</guid>
      <description>&lt;p&gt;Your backtest equity curve is merely &lt;strong&gt;one historical trajectory out of millions of equally probable alternatives&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The total profit of your trading system is invariant to order: 100 closed trades will yield the exact same ending dollar return whether the winning trades arrive first or last. However, &lt;strong&gt;path-dependent survivability metrics&lt;/strong&gt;—Maximum Drawdown, Ulcer Index, Margin Call Probability, and Time-to-Recovery—are dominated by trade sequence.&lt;/p&gt;

&lt;p&gt;If a cluster of 6 normal consecutive losses strikes at trade #1 instead of trade #70, an account operating under a strict 10% risk floor (common in institutional mandates and proprietary evaluation rules) is liquidated before the positive edge ever materializes.&lt;/p&gt;

&lt;p&gt;In this article, we formulate and implement a &lt;strong&gt;vectorized Monte Carlo permutation engine in Python&lt;/strong&gt; to rigorously falsify equity curves before committing live capital.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Permutation vs. Bootstrap: The Critical Distinction
&lt;/h2&gt;

&lt;p&gt;Many trading platforms market "Monte Carlo analysis" without disclosing their resampling methodology. In quantitative finance, the distinction is fundamental:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Permutation Testing (Without Replacement)&lt;/th&gt;
&lt;th&gt;Bootstrap Resampling (With Replacement)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Random shuffle of historical trade order&lt;/td&gt;
&lt;td&gt;Random draw where any trade can be selected $k$ times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P&amp;amp;L Distribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100% Identical&lt;/strong&gt; to sample (same mean, win rate, skewness)&lt;/td&gt;
&lt;td&gt;Creates synthetic distributions with altered win rates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Statistical Target&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Isolates &lt;strong&gt;sequence risk&lt;/strong&gt; alone&lt;/td&gt;
&lt;td&gt;Estimates sampling error under $i.i.d.$ assumption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vulnerability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assumes trades are conditionally independent&lt;/td&gt;
&lt;td&gt;Heavily distorts tail risk if outliers are oversampled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For validating an existing trading journal or strategy backtest, &lt;strong&gt;Permutation Testing is the strict falsification baseline&lt;/strong&gt;. It asks the minimal, humble question: &lt;em&gt;"Given the exact trades we actually took, what percentage of alternate timelines would have breached our risk budget?"&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The 4 Path Metrics That Expose Hidden Ruin
&lt;/h2&gt;

&lt;p&gt;Looking solely at backtest Sharpe ratio or nominal net profit hides ruin. When running $N=5,000$ permutations, evaluate these four metrics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Probability of Ruin ($P_{\text{ruin}}$):&lt;/strong&gt; The percentage of shuffled paths whose equity breaches the hard drawdown barrier (e.g., $9,000$ on a $\$10,000$ starting balance) at any point along the horizon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Median Max Drawdown ($MDD_{50}$):&lt;/strong&gt; The central tendency of drawdown. If your backtest showed a 7% drawdown but the median shuffled drawdown is 16%, your historical curve was an unusually lucky sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;95th Percentile Max Drawdown ($MDD_{95}$):&lt;/strong&gt; The stress-test boundary. 95% of simulated timelines experienced a drawdown less severe than this number; 5% suffered worse. This is your true operational capital requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawdown Dispersion Ratio:&lt;/strong&gt; $MDD_{95} / MDD_{50}$. A high ratio indicates severe tail-sequence vulnerability.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Vectorized Python Implementation (NumPy)
&lt;/h2&gt;

&lt;p&gt;Iterating across 5,000 simulations using Python loops is computationally inefficient. Below is a high-performance, fully vectorized implementation using &lt;code&gt;numpy.random.default_rng().permuted&lt;/code&gt;:&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;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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_monte_carlo_permutation&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;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;initial_capital&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;10000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ruin_capital&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;9000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_simulations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&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;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Vectorized Monte Carlo Permutation Test (Resampling Without Replacement).
    Evaluates sequence risk and ruin probability on trade P&amp;amp;L arrays.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;trades&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;asarray&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;dtype&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="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;n_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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n_trades&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;At least 10 trades required for meaningful permutation testing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rng&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="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 1. Broadcast and independently shuffle across simulations (without replacement)
&lt;/span&gt;    &lt;span class="n"&gt;sim_matrix&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;tile&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_simulations&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="n"&gt;shuffled_trades&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permuted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_matrix&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="c1"&gt;# 2. Vectorized cumulative equity trajectories
&lt;/span&gt;    &lt;span class="n"&gt;cumulative_pnl&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;cumsum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled_trades&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="n"&gt;initial_col&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;full&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;num_simulations&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="n"&gt;initial_capital&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;equity_paths&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;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;initial_col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_capital&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cumulative_pnl&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Peak equity and path drawdowns
&lt;/span&gt;    &lt;span class="n"&gt;running_peaks&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="n"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accumulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equity_paths&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="n"&gt;drawdowns_dollar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;running_peaks&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;equity_paths&lt;/span&gt;
    &lt;span class="n"&gt;drawdowns_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drawdowns_dollar&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;running_peaks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Max drawdown per trajectory
&lt;/span&gt;    &lt;span class="n"&gt;max_mdd_pct&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;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drawdowns_pct&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="c1"&gt;# 5. Ruin evaluation (breaching floor at any point in time)
&lt;/span&gt;    &lt;span class="n"&gt;min_equity_per_path&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;equity_paths&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="n"&gt;ruin_paths&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_equity_per_path&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;ruin_capital&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prob_ruin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruin_paths&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;num_simulations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_trades&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n_trades&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_capital&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial_capital&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ruin_capital&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ruin_capital&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nominal_net_pnl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&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;sum&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;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_simulations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num_simulations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prob_ruin_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_ruin&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mdd_p05_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&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;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_mdd_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mdd_p50_median_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&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;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_mdd_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mdd_p95_worst_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&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;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_mdd_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;falsification_verdict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REJECT_EXCESSIVE_RUIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prob_ruin&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASS_ROBUST_SEQUENCE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Empirical Case Study: The "Profitable" Strategy That Blows Up
&lt;/h2&gt;

&lt;p&gt;Consider a 100-trade sample with a &lt;strong&gt;52% win rate&lt;/strong&gt; and positive net expectation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;52 winning trades (mean profit ~\$170)&lt;/li&gt;
&lt;li&gt;48 losing trades (mean loss ~-\$170, with occasional -\$350 tail losses)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nominal Net P&amp;amp;L:&lt;/strong&gt; &lt;code&gt;+$505.39&lt;/code&gt; on a &lt;code&gt;\$10,000&lt;/code&gt; account (profitable on paper).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we execute &lt;code&gt;run_monte_carlo_permutation&lt;/code&gt; over 5,000 shuffles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Empirical verification run
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_monte_carlo_permutation&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="n"&gt;trades_sample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;initial_capital&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ruin_capital&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;9000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 10% maximum drawdown ceiling
&lt;/span&gt;    &lt;span class="n"&gt;num_simulations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exact Output:
&lt;/h3&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;"n_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;100&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_capital"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ruin_capital"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nominal_net_pnl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;505.39&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"num_simulations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prob_ruin_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mdd_p05_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.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;"mdd_p50_median_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;16.77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mdd_p95_worst_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"falsification_verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REJECT_EXCESSIVE_RUIN"&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;h3&gt;
  
  
  The Institutional Reality:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Even though the original single backtest ended with &lt;strong&gt;+$505.39 net profit&lt;/strong&gt;, &lt;strong&gt;42.64% of all possible alternate histories breached the 10% drawdown barrier&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In 95% of histories, the drawdown reached up to &lt;strong&gt;25.50%&lt;/strong&gt;—more than double the allowable risk budget.&lt;/li&gt;
&lt;li&gt;Without Monte Carlo permutation, a developer deploying this system would falsely attribute failure to "market regime change" or "bad luck", when in reality the strategy was statistically insolvent against basic sequence risk from day one.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Methodological Boundaries (What Monte Carlo Cannot Do)
&lt;/h2&gt;

&lt;p&gt;A rigorous quant must understand the falsification limits of permutation testing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Serial Autocorrelation Breakdown:&lt;/strong&gt; Permutation randomly destroys the temporal ordering of trades. If your strategy has positive autocorrelation in losses (e.g., clustered losses during high-volatility macro announcements), permutation may underestimate clustering severity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-Distribution Shocks:&lt;/strong&gt; Shuffling only explores combinations of &lt;em&gt;events that already occurred&lt;/em&gt;. It cannot simulate a 5-sigma liquidity flash crash if none was present in your historical log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journal vs. Market:&lt;/strong&gt; Monte Carlo evaluates the statistical fragility of the &lt;em&gt;trade sequence&lt;/em&gt;, not the underlying market microstructure.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  6. Open Source Implementation &amp;amp; References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scientific DOI:&lt;/strong&gt; Reproducible quant code registered under CERN Zenodo: &lt;a href="https://doi.org/10.5281/zenodo.22012203" rel="noopener noreferrer"&gt;&lt;code&gt;10.5281/zenodo.22012203&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/guetaquant-byte/guetaquant-tools" rel="noopener noreferrer"&gt;&lt;code&gt;guetaquant-byte/guetaquant-tools&lt;/code&gt;&lt;/a&gt; (AGPLv3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Tool:&lt;/strong&gt; Browser-based Monte Carlo permutation is integrated into the client-side &lt;a href="https://guetaquant.com/journal/" rel="noopener noreferrer"&gt;Local-First Trading Journal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Depth Study:&lt;/strong&gt; Full mathematical derivation and percentile bands at &lt;a href="https://guetaquant.com/blog/monte-carlo-trading/" rel="noopener noreferrer"&gt;Gueta Quant Monte Carlo Analysis&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Aviso Regulatorio (SFC Colombia — Decreto 2555 de 2010): Este artículo tiene un propósito 100% pedagógico, educativo y de investigación en ingeniería de software financiero. Gueta Quant no presta asesoría financiera ni emite señales de inversión.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By **Mahdi Goodarzi&lt;/em&gt;* (&lt;a href="https://g.dev/mahdigoodarzi" rel="noopener noreferrer"&gt;g.dev/mahdigoodarzi&lt;/a&gt;), Founder of Gueta Quant.*&lt;/p&gt;

</description>
      <category>python</category>
      <category>quant</category>
      <category>algorithmictrading</category>
      <category>datascience</category>
    </item>
    <item>
      <title>cTrader cBot Risk Engine: High-Precision C# Dynamic Stop-Loss Architecture</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Mon, 21 Sep 2026 08:36:17 +0000</pubDate>
      <link>https://dev.to/guetaquant/ctrader-cbot-risk-engine-high-precision-c-dynamic-stop-loss-architecture-3kpc</link>
      <guid>https://dev.to/guetaquant/ctrader-cbot-risk-engine-high-precision-c-dynamic-stop-loss-architecture-3kpc</guid>
      <description>&lt;p&gt;Static stop-losses break across volatility regimes. A fixed 20-pip stop that is conservative on EUR/USD during Asia session is noise-level on XAU/USD during New York open. In cTrader Automate (C#), the robust pattern is a dedicated risk engine: signal logic decides direction, the engine decides size and stop distance from live volatility.&lt;/p&gt;

&lt;p&gt;This post documents the architecture — educational, no trade signals, no return claims.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Separate Sizing From Signals
&lt;/h2&gt;

&lt;p&gt;The most common cBot flaw is inline risk math inside &lt;code&gt;OnBar()&lt;/code&gt;: volume computed next to entry conditions, stop distance hardcoded. When volatility doubles, the same stop gets hunted and the same volume carries twice the dollar risk.&lt;/p&gt;

&lt;p&gt;Split the cBot into two units:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal module&lt;/strong&gt; — produces direction only (e.g., breakout, trend filter).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk engine&lt;/strong&gt; — given direction, computes stop distance from ATR and volume from a fixed monetary budget.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// RiskEngine.cs — pure logic, no cTrader dependencies (unit-testable)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RiskEngine&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;StopDistancePrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;)&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;atr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;VolumeInUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;riskMoney&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;stopDistancePrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tickValuePerUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;pipSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;volumeMin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;volumeMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;volumeStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;clampedToMin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;effectiveRisk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;riskMoney&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopDistancePrice&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;pipSize&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tickValuePerUnit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;floored&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;volumeStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;volumeStep&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;clampedToMin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;floored&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;volumeMin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;floored&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volumeMin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;clampedToMin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;finalVolume&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&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;floored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volumeMax&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;effectiveRisk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;finalVolume&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopDistancePrice&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;pipSize&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tickValuePerUnit&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;finalVolume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;clampedToMin&lt;/code&gt; flag: when the budgeted volume falls below the broker minimum, the engine clamps up &lt;strong&gt;and reports the effective risk&lt;/strong&gt;, which now exceeds the budget. Silently reporting the original budget while carrying higher exposure is the exact failure we dissected in &lt;a href="https://coderlegion.com/27300/the-silent-position-sizer-bug-when-accounts-are-small-your-risk-inadvertently-doubles" rel="noopener noreferrer"&gt;The Silent Position Sizer Bug&lt;/a&gt; — never repeat it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. ATR-Based Dynamic Stops
&lt;/h2&gt;

&lt;p&gt;Average True Range adapts the stop to current volatility. A 1.5x–2.0x ATR stop keeps the exit outside normal noise on any symbol and any session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;GetAtrStopPips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;atrPeriod&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AverageTrueRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atrPeriod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MovingAverageType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exponential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;stopPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastValue&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&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;stopPrice&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PipSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the stop distance is measured, not assumed, the same cBot behaves sanely on a 0.0001-pip Forex pair and a 2-decimal metal without retuning magic numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Wiring It Into the cBot
&lt;/h2&gt;

&lt;p&gt;Volume in cTrader is expressed in units (100,000 units = 1.0 standard lot on FX). Always read the broker limits from the symbol — never hardcode them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnBar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// single-position example&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;GetSignal&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;stopPips&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetAtrStopPips&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;stopPrice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stopPips&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PipSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RiskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VolumeInUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;RiskMoney&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TickValue&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TickSize&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PipSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PipSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VolumeInUnitsMin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VolumeInUnitsMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VolumeInUnitsStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;clamped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;effectiveRisk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clamped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WARNING: volume clamped to broker minimum. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
              &lt;span class="s"&gt;"Effective risk {0} exceeds budget {1}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;effectiveRisk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RiskMoney&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;ExecuteMarketOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TradeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SymbolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="s"&gt;"atr-risk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopPips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Trailing Without Chasing
&lt;/h2&gt;

&lt;p&gt;Move the stop only when price has banked a multiple of the ATR distance — ratcheting on every tick bleeds to spread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TrailAtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;atrDistancePrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;newStop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bid&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;atrDistancePrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// long example&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newStop&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopLoss&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PipSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;ModifyPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newStop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TakeProfit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Validation Checklist (Demo First)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run 60 days on demo across at least two volatility regimes (e.g., Forex + metal).&lt;/li&gt;
&lt;li&gt;Log every clamp event; if clamps exceed ~5% of trades, the risk budget is too small for the account — top up or widen the stop, never silence the warning.&lt;/li&gt;
&lt;li&gt;Backtest with real-tick data; reject any result you cannot reproduce forward on demo.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Educational content under Colombia SFC Decreto 2555 de 2010: this is financial-engineering study material, not investment advice. No signals, no managed accounts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By **Mahdi Goodarzi&lt;/em&gt;* (&lt;a href="https://g.dev/mahdigoodarzi" rel="noopener noreferrer"&gt;g.dev/mahdigoodarzi&lt;/a&gt;), Gueta Quant — interactive risk tools at &lt;a href="https://guetaquant.com" rel="noopener noreferrer"&gt;guetaquant.com&lt;/a&gt;. Companion guide: &lt;a href="https://guetaquant.com/ctrader-copy-trading/" rel="noopener noreferrer"&gt;cTrader Copy setup in Colombia&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>ctrader</category>
      <category>algorithmictrading</category>
      <category>quant</category>
    </item>
    <item>
      <title>El bug silencioso de las calculadoras de lotaje: cuando la cuenta es pequeña, tu riesgo se duplica</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:14:42 +0000</pubDate>
      <link>https://dev.to/guetaquant/el-bug-silencioso-de-las-calculadoras-de-lotaje-cuando-la-cuenta-es-pequena-tu-riesgo-se-duplica-18jf</link>
      <guid>https://dev.to/guetaquant/el-bug-silencioso-de-las-calculadoras-de-lotaje-cuando-la-cuenta-es-pequena-tu-riesgo-se-duplica-18jf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aviso.&lt;/strong&gt; GuetaQuant es un portal de educación cuantitativa independiente. Este artículo es material pedagógico sobre implementación de software de gestión de riesgo. No es asesoría financiera, ni recomendación de inversión, ni captación de fondos. Operar en mercados apalancados conlleva riesgo de pérdida total del capital.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Casi todo el material sobre dimensionamiento de posición se detiene en la misma ecuación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lotes = (balance × riesgo%) / (distancia_stop × valor_por_lote)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Es correcta, y es la parte fácil. El problema no está en la fórmula: está en las tres líneas que vienen después, cuando hay que ajustar ese número al paso de lote y al mínimo que exige el broker.&lt;/p&gt;

&lt;p&gt;Ahí es donde encontramos un bug en nuestro propio código. Lo publicamos porque el patrón está en todas partes, y porque el error se concentra exactamente en las cuentas más pequeñas.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. El bug: &lt;code&gt;MathMax(minLot, lotes)&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Este es el cierre habitual de una función de lotaje. Aparece en foros de MQL5, en repositorios de GitHub y estaba en el fragmento que nosotros mismos publicábamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double lots = MathFloor(rawLot / lotStep) * lotStep;
return MathMax(minLot, MathMin(maxLot, lots));   // &amp;lt;-- aquí
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MathFloor&lt;/code&gt; está bien: redondear hacia abajo garantiza no pasarse del presupuesto. El problema es &lt;code&gt;MathMax(minLot, ...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cuando el lote presupuestado queda &lt;strong&gt;por debajo&lt;/strong&gt; del mínimo del broker, &lt;code&gt;MathFloor&lt;/code&gt; devuelve &lt;code&gt;0.00&lt;/code&gt;. &lt;code&gt;MathMax&lt;/code&gt; entonces lo sube a &lt;code&gt;minLot&lt;/code&gt;. La función devuelve un lote operable و no dice nada.&lt;/p&gt;

&lt;p&gt;Ese lote ya no respeta el presupuesto. Y la función sigue reportando el presupuesto original como si fuera el riesgo asumido.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. La medición
&lt;/h2&gt;

&lt;p&gt;Cuenta de 500 USD. Riesgo 0.5%. Stop de 500 ticks, valor del tick 1.00 USD.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concepto&lt;/th&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Presupuesto de riesgo (0.5% de 500)&lt;/td&gt;
&lt;td&gt;2.50 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lotes brutos&lt;/td&gt;
&lt;td&gt;0.005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;floor&lt;/code&gt; al paso de 0.01&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Devuelto tras &lt;code&gt;MathMax(minLot, …)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Riesgo real de esos 0.01 lotes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.00 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Riesgo reportado por la función&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.50 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;El riesgo efectivo es &lt;strong&gt;el doble&lt;/strong&gt; del presupuestado, y nada en la respuesta lo indica.&lt;/p&gt;

&lt;p&gt;No es un caso de laboratorio: es una cuenta de 500 USD operando con riesgo conservador. Es el perfil de quien más necesita que el límite se respete.&lt;/p&gt;

&lt;h3&gt;
  
  
  El mismo problema con &lt;code&gt;round()&lt;/code&gt; en lugar de &lt;code&gt;floor()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Una variante frecuente es usar &lt;code&gt;round()&lt;/code&gt; en vez de &lt;code&gt;floor()&lt;/code&gt;. Buscamos por fuerza bruta el peor caso sobre una malla de balances (500–20.000 USD), stops (5–120 pips) y riesgos (0.5%, 1%, 2%):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paso de lote&lt;/th&gt;
&lt;th&gt;Peor sobrepaso medido&lt;/th&gt;
&lt;th&gt;Caso&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+99.15%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;balance 1.175 USD, riesgo 0.5%, stop 117 pips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+99.58%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;balance 12.025 USD, riesgo 0.5%, stop 120 pips&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;En ambos casos los lotes brutos caen justo por encima de la mitad del paso (0.0050 y 0.0501), &lt;code&gt;round()&lt;/code&gt; sube al siguiente escalón y el riesgo casi se duplica. El sobrepaso no está acotado a "unos centavos": está acotado a medio paso de lote, que cerca del mínimo equivale a un 100% del presupuesto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Y un tercer caso: el valor del pip quemado en el código
&lt;/h3&gt;

&lt;p&gt;Muchas implementaciones asumen &lt;code&gt;valor_pip = 10 USD&lt;/code&gt;, que solo es cierto para pares con USD como divisa cotizada y lote estándar. En XAUUSD el valor real ronda 1 USD por pip.&lt;/p&gt;

&lt;p&gt;Con balance de 10.000 USD, riesgo 1% (presupuesto 100 USD) y stop de 320 pips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asumiendo 10 USD/pip: lotes brutos &lt;code&gt;0.0312&lt;/code&gt; → riesgo real &lt;strong&gt;9.60 USD&lt;/strong&gt;. Se arriesga &lt;strong&gt;una décima parte&lt;/strong&gt; de lo previsto, y la estrategia queda infra-dimensionada sin que nadie lo note.&lt;/li&gt;
&lt;li&gt;Con el valor real de 1 USD/pip: lotes brutos &lt;code&gt;0.3125&lt;/code&gt; → riesgo real &lt;strong&gt;99.20 USD&lt;/strong&gt;. Correcto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este falla en silencio y hacia abajo, que es peor de detectar: nadie revisa una función de riesgo porque esté arriesgando de menos.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. El arreglo
&lt;/h2&gt;

&lt;p&gt;La regla es simple: &lt;strong&gt;si el lote presupuestado no llega al mínimo del broker, la operación no cabe dentro del riesgo definido.&lt;/strong&gt; Subirla a &lt;code&gt;minLot&lt;/code&gt; no es un ajuste, es cambiar el presupuesto sin avisar.&lt;/p&gt;

&lt;p&gt;La función debe rechazar la entrada y devolver el dato para que decida la persona:&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;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tamano_posicion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;riesgo_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sl_ticks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor_tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;paso_lote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lote_min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lote_max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;presupuesto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;riesgo_pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;perdida_por_lote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sl_ticks&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;valor_tick&lt;/span&gt;
    &lt;span class="n"&gt;lotes_brutos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;presupuesto&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;perdida_por_lote&lt;/span&gt;

    &lt;span class="n"&gt;lotes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lotes_brutos&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;paso_lote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;paso_lote&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lotes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;lote_min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;motivo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;el lote presupuestado esta por debajo del minimo del broker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lotes_brutos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;lotes_brutos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;presupuesto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;presupuesto&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;riesgo_si_usas_el_minimo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;lote_min&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;perdida_por_lote&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="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;lotes&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;lotes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lote_max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;riesgo_real&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lotes&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;perdida_por_lote&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lotes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;lotes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;presupuesto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;presupuesto&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;riesgo_real&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;riesgo_real&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salida real de las dos llamadas:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;tamano_posicion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operable&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;motivo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;el lote presupuestado esta por debajo del minimo del broker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lotes_brutos&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;presupuesto&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;riesgo_si_usas_el_minimo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;tamano_posicion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&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="mi"&gt;500&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operable&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lotes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;presupuesto&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;riesgo_real&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El primer caso no devuelve un lote. Devuelve el motivo y el número que hay que mirar: operar el mínimo cuesta 5.00 USD contra un presupuesto de 2.50. La decisión de aceptar ese riesgo es del trader, no de una línea de código que se lo oculta.&lt;/p&gt;

&lt;p&gt;Y en MQL5:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double lots = MathFloor(rawLot / lotStep) * lotStep;

// Si el lote presupuestado no alcanza el minimo del broker, la operacion NO cabe
// dentro del riesgo definido. Subirlo a minLot arriesgaria mas de lo presupuestado.
if(lots &amp;lt; minLot) return 0.0;

return MathMin(maxLot, lots);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Tres reglas para revisar tu propia implementación
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nunca &lt;code&gt;round()&lt;/code&gt;, siempre &lt;code&gt;floor()&lt;/code&gt;.&lt;/strong&gt; El sobrepaso de &lt;code&gt;round()&lt;/code&gt; llega a medio paso de lote, que cerca del mínimo es ~100% del presupuesto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nunca subir a &lt;code&gt;minLot&lt;/code&gt; en silencio.&lt;/strong&gt; O se rechaza la entrada, o se devuelve el riesgo efectivo junto con una bandera de que hubo ajuste. Devolver solo el presupuesto teórico es reportar un número falso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nunca quemar el valor del pip.&lt;/strong&gt; Léelo del símbolo (&lt;code&gt;SYMBOL_TRADE_TICK_VALUE&lt;/code&gt; y &lt;code&gt;SYMBOL_TRADE_TICK_SIZE&lt;/code&gt; en MQL5). Un &lt;code&gt;10.0&lt;/code&gt; fijo rompe en oro, índices y cripto.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. Nota de transparencia
&lt;/h2&gt;

&lt;p&gt;Este bug estaba en nuestro propio código, en el endpoint público &lt;code&gt;/api/position-size/&lt;/code&gt;, que devolvía &lt;code&gt;roundedLots: 0.01&lt;/code&gt; junto a &lt;code&gt;riskAmount: 2.5&lt;/code&gt; cuando el riesgo real era 5.00 USD. Ya está corregido: la respuesta ahora incluye &lt;code&gt;effectiveRiskAmount&lt;/code&gt;, &lt;code&gt;effectiveRiskPercent&lt;/code&gt;, &lt;code&gt;clampedToMin&lt;/code&gt; y &lt;code&gt;clampedToMax&lt;/code&gt;, y hay dos pruebas de regresión que fallan si alguien vuelve a ocultar el ajuste.&lt;/p&gt;

&lt;p&gt;La calculadora web sí mostraba la advertencia correctamente desde el principio; el fallo estaba en la librería que alimenta la API y en el fragmento MQL5 publicado. Publicamos el error porque un portal que enseña gestión de riesgo y esconde sus propios defectos no sirve para nada.&lt;/p&gt;

&lt;p&gt;La calculadora, la fórmula completa y el indicador en MQL5 bajo licencia AGPLv3 están acá:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://guetaquant.com/herramientas/" rel="noopener noreferrer"&gt;Calculadora de lotaje MT5 — GuetaQuant&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;¿Cómo maneja tu implementación el caso de lote por debajo del mínimo? Me interesa saber si alguien lo rechaza, lo clampa o lo reporta.&lt;/p&gt;

</description>
      <category>python</category>
      <category>quant</category>
      <category>trading</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Algorithmic Risk Management in MT5: ATR Volatility Position Sizing &amp; MQL5 Architecture</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:30:02 +0000</pubDate>
      <link>https://dev.to/guetaquant/algorithmic-risk-management-in-mt5-atr-volatility-position-sizing-mql5-architecture-55nn</link>
      <guid>https://dev.to/guetaquant/algorithmic-risk-management-in-mt5-atr-volatility-position-sizing-mql5-architecture-55nn</guid>
      <description>&lt;h1&gt;
  
  
  Algorithmic Risk Management in MT5: Dynamic Position Sizing &amp;amp; MQL5 Architecture
&lt;/h1&gt;

&lt;p&gt;Most retail trading failures stem from static lot sizing. Trading a fixed 1.0 lot on EUR/USD creates vastly different dollar drawdowns compared to 1.0 lot on Gold (XAU/USD) or NASDAQ (NAS100) due to differing point values and underlying volatility regimes.&lt;/p&gt;

&lt;p&gt;In professional quantitative finance, position sizing is dynamically governed by the asset's Average True Range (ATR) and a fixed risk budget per trade:&lt;/p&gt;

&lt;p&gt;$$\text{Position Size (Lots)} = \frac{\text{Account Balance} \times \text{Risk \%}}{\text{Stop Loss Distance (Points)} \times \text{Tick Value}}$$&lt;/p&gt;

&lt;p&gt;In this technical guide, we implement an open-source, production-grade MQL5 risk engine that dynamically computes lot sizes and routes orders with slippage protection.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Original interactive tool and web calculator available at &lt;a href="https://guetaquant.com/herramientas/mt5-position-sizer/" rel="noopener noreferrer"&gt;GuetaQuant MT5 Position Sizer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. MQL5 Dynamic Lot Calculation Class
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//+------------------------------------------------------------------+
//|                                             GQ_RiskEngine.mqh    |
//|                             Copyright 2026, Gueta Quant (AGPLv3) |
//|                                       https://guetaquant.com     |
//+------------------------------------------------------------------+
#property copyright "Gueta Quant"
#property link      "https://guetaquant.com"

class CGQRiskManager
{
private:
   string   m_symbol;
   double   m_risk_pct;

public:
   CGQRiskManager(string symbol, double risk_pct) : m_symbol(symbol), m_risk_pct(risk_pct) {}

   double CalculateLots(double sl_distance_price)
   {
      if (sl_distance_price &amp;lt;= 0) return 0.0;

      double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
      double risk_amount = balance * (m_risk_pct / 100.0);

      double tick_size   = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);
      double tick_value  = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
      double min_lot     = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);
      double max_lot     = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX);
      double lot_step    = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP);

      if (tick_size == 0 || tick_value == 0) return 0.0;

      double loss_per_lot = (sl_distance_price / tick_size) * tick_value;
      if (loss_per_lot &amp;lt;= 0) return 0.0;

      double raw_lots = risk_amount / loss_per_lot;

      // Normalize to broker lot step
      double normalized_lots = MathFloor(raw_lots / lot_step) * lot_step;

      if (normalized_lots &amp;lt; min_lot) normalized_lots = min_lot;
      if (normalized_lots &amp;gt; max_lot) normalized_lots = max_lot;

      return normalized_lots;
   }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. ATR Volatility Trailing Stops
&lt;/h2&gt;

&lt;p&gt;Using historical volatility avoids getting stopped out during normal market noise while protecting capital during structural trend shifts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double GetATRDistance(string symbol, ENUM_TIMEFRAMES tf, int period, double multiplier)
{
   int handle = iATR(symbol, tf, period);
   if (handle == INVALID_HANDLE) return 0.0;

   double atr_val[1];
   if (CopyBuffer(handle, 0, 0, 1, atr_val) &amp;lt;= 0) return 0.0;

   return atr_val[0] * multiplier;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Open Source Tools Ecosystem
&lt;/h2&gt;

&lt;p&gt;All our quantitative software is published under open-source AGPLv3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;44 Open Source Trading Tools:&lt;/strong&gt; &lt;a href="https://github.com/guetaquant-byte/guetaquant-tools" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CERN Zenodo DOI Registry:&lt;/strong&gt; &lt;a href="https://doi.org/10.5281/zenodo.22012203" rel="noopener noreferrer"&gt;DOI 10.5281/zenodo.22012203&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-First Trading Journal:&lt;/strong&gt; &lt;a href="https://guetaquant.com/journal/" rel="noopener noreferrer"&gt;GuetaQuant Journal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Compliance: Strictly educational and research material. Does not constitute financial advice. Compliant with Colombian SFC Decreto 2555/2010.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>metatrader5</category>
      <category>quant</category>
    </item>
    <item>
      <title>Pine Script v6 in 2026: math.sum, math.tanh, User Defined Types &amp; Risk Management Engine</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:52:14 +0000</pubDate>
      <link>https://dev.to/guetaquant/pine-script-v6-in-2026-mathsum-mathtanh-user-defined-types-risk-management-engine-53b4</link>
      <guid>https://dev.to/guetaquant/pine-script-v6-in-2026-mathsum-mathtanh-user-defined-types-risk-management-engine-53b4</guid>
      <description>&lt;h2&gt;
  
  
  Overview: Pine Script v6 &amp;amp; Institutional Risk Architecture
&lt;/h2&gt;

&lt;p&gt;TradingView's &lt;strong&gt;Pine Script v6&lt;/strong&gt; introduced critical architectural upgrades for quantitative developers. Moving beyond basic visual scripts, v6 provides strict type enforcement, built-in tensor-like math functions (&lt;code&gt;math.sum()&lt;/code&gt;, &lt;code&gt;math.tanh()&lt;/code&gt;), User-Defined Types (UDTs), and native webhook JSON formatting for institutional order routing.&lt;/p&gt;

&lt;p&gt;In this guide, we break down the most essential v6 syntax upgrades and build a full production-ready risk engine with volatility-adjusted sizing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Original in-depth research published at &lt;a href="https://guetaquant.com/pine-script-v6/" rel="noopener noreferrer"&gt;GuetaQuant&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Key Mathematical Upgrades in Pine Script v6
&lt;/h2&gt;

&lt;p&gt;In previous versions (v5), performing mathematical reductions across arrays or custom series required cumbersome loops. Pine Script v6 optimizes computational performance by executing core vector operations natively in C++:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;math.sum()&lt;/code&gt; &amp;amp; &lt;code&gt;math.tanh()&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@version=6
indicator("Pine Script v6 Math &amp;amp; Non-Linear Activation", overlay=false)

// Native vector sum across an array of returns
var float[] log_returns = array.new_float(0)
float current_return = math.log(close / close[1])
array.push(log_returns, current_return)
if array.size(log_returns) &amp;gt; 50
    array.shift(log_returns)

// Hyperbolic tangent non-linear activation (useful for signal bounding [-1.0, 1.0])
float normalized_zscore = (close - ta.sma(close, 20)) / ta.stdev(close, 20)
float bounded_signal = math.tanh(normalized_zscore)

plot(bounded_signal, "Tanh Signal", color=color.emerald)
hline(0.8, "Upper Bound", color=color.gray)
hline(-0.8, "Lower Bound", color=color.gray)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Object-Oriented Architecture with User-Defined Types (UDTs)
&lt;/h2&gt;

&lt;p&gt;Pine Script v6 fully embraces struct-like User-Defined Types. This allows you to encapsulate complete trade contexts (entry, stop loss, risk budget, target) into a single object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@version=6
indicator("GQ Trade Context Engine", overlay=true)

type TradeRiskContext
    string symbol
    float entryPrice
    float stopLossPrice
    float lotSize
    float riskAmountUSD

// Factory method to calculate ATR-based risk
fn_create_context(float risk_pct, int atr_len, float atr_mult) =&amp;gt;
    float atr_val = ta.atr(atr_len)
    float sl_distance = atr_val * atr_mult
    float sl_price = close - sl_distance
    float risk_dollars = (strategy.equity * risk_pct) / 100.0
    float calculated_lots = risk_dollars / (sl_distance * 10.0) // 10 USD per pip standard

    TradeRiskContext.new(syminfo.ticker, close, sl_price, calculated_lots, risk_dollars)

var TradeRiskContext active_trade = na
if ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
    active_trade := fn_create_context(1.0, 14, 2.5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Webhook JSON Alert Formatting for Automated Execution
&lt;/h2&gt;

&lt;p&gt;Routing alerts to MetaTrader 5 (MT5), cTrader, or custom Python execution servers requires strictly formatted JSON payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@version=6
strategy("GQ Webhook Strategy - Pine v6", overlay=true, initial_capital=10000)

fn_build_order_json(string action, float size, float sl_price) =&amp;gt;
    '{"action":"' + action + '","symbol":"' + syminfo.ticker + '","lots":' + str.tostring(size) + ',"sl":' + str.tostring(sl_price) + '}'

if ta.crossover(ta.sma(close, 20), ta.sma(close, 50))
    string payload = fn_build_order_json("BUY", 0.50, close - 2.5 * ta.atr(14))
    strategy.entry("Long", strategy.long, alert_message=payload)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Explore More Open-Source Tools
&lt;/h2&gt;

&lt;p&gt;All our MQL5 EAs, cTrader cBots, and Pine Script v6 indicators are open-source under AGPLv3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;44 Open Source Quantitative Tools:&lt;/strong&gt; &lt;a href="https://github.com/guetaquant-byte/guetaquant-tools" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive MT5 Lot Size Calculator:&lt;/strong&gt; &lt;a href="https://guetaquant.com/herramientas/mt5-position-sizer/" rel="noopener noreferrer"&gt;GuetaQuant MT5 Position Sizer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-First Quantitative Trading Journal:&lt;/strong&gt; &lt;a href="https://guetaquant.com/journal/" rel="noopener noreferrer"&gt;GuetaQuant Journal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: Educational research only. Does not constitute investment advice. Compliance with SFC Colombia Decreto 2555/2010.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tradingview</category>
      <category>algorithmictrading</category>
      <category>pinescript</category>
      <category>quant</category>
    </item>
    <item>
      <title>Pine Script v6 in 2026: math.sum, math.tanh, User Defined Types &amp; Risk Management Engine</title>
      <dc:creator>Gueta Quant </dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:44:24 +0000</pubDate>
      <link>https://dev.to/guetaquant/pine-script-v6-in-2026-mathsum-mathtanh-user-defined-types-risk-management-engine-2426</link>
      <guid>https://dev.to/guetaquant/pine-script-v6-in-2026-mathsum-mathtanh-user-defined-types-risk-management-engine-2426</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'endraw'&lt;/p&gt;
</description>
      <category>tradingview</category>
      <category>algorithmictrading</category>
      <category>pinescript</category>
      <category>quant</category>
    </item>
  </channel>
</rss>
