<?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: Gary McLaughlin</title>
    <description>The latest articles on DEV Community by Gary McLaughlin (@gary_mclaughlin_d125db1e4).</description>
    <link>https://dev.to/gary_mclaughlin_d125db1e4</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%2F3934310%2F544360c6-d0a4-4927-9e0f-f281635244b3.jpg</url>
      <title>DEV Community: Gary McLaughlin</title>
      <link>https://dev.to/gary_mclaughlin_d125db1e4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gary_mclaughlin_d125db1e4"/>
    <language>en</language>
    <item>
      <title>The baseline that quietly stopped meaning anything (and closed real trades)</title>
      <dc:creator>Gary McLaughlin</dc:creator>
      <pubDate>Sun, 30 Aug 2026 06:51:28 +0000</pubDate>
      <link>https://dev.to/gary_mclaughlin_d125db1e4/the-baseline-that-quietly-stopped-meaning-anything-and-closed-real-trades-1jhl</link>
      <guid>https://dev.to/gary_mclaughlin_d125db1e4/the-baseline-that-quietly-stopped-meaning-anything-and-closed-real-trades-1jhl</guid>
      <description>&lt;p&gt;Last week a program I wrote closed every open position on a live trading account and refused to do any more work for the rest of the day.&lt;/p&gt;

&lt;p&gt;Nothing had gone wrong. The owner had moved some of their own money out of the account.&lt;/p&gt;

&lt;p&gt;The bug turned out to be a shape I have written a dozen times in completely unrelated systems, so it is worth writing down.&lt;/p&gt;

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

&lt;p&gt;The program guards against losing too much in a day. It stores a baseline when the day starts and compares the current value against it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;dayPnL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentEquity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dayStartEquity&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;dayPnL&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dailyLossLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;closeEverything&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;stopForTheDay&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;That is correct. It is also completely wrong, and both statements are true at the same time.&lt;/p&gt;

&lt;p&gt;It is correct for the thing it was written to measure: money won or lost by trading. It is wrong the moment the underlying value can change for a reason that is not trading — someone withdrawing funds. Equity drops by 500. &lt;code&gt;dayPnL&lt;/code&gt; reads -500. The guard concludes the day has gone catastrophically badly and liquidates positions that were doing nothing wrong.&lt;/p&gt;

&lt;p&gt;A deposit produces the mirror image: the profit target is hit by money that was simply added.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual bug
&lt;/h2&gt;

&lt;p&gt;The baseline and the measurement had silently stopped meaning the same thing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dayStartEquity&lt;/code&gt; means &lt;em&gt;what the account was worth before today's trading&lt;/em&gt;. &lt;code&gt;currentEquity&lt;/code&gt; means &lt;em&gt;what the account is worth now&lt;/em&gt;. Subtract them and you get today's trading result &lt;strong&gt;only if trading is the sole thing that can move the number.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nobody wrote that assumption down. It was true when the code was written and it stayed true right up until it wasn't.&lt;/p&gt;

&lt;p&gt;This is not a trading bug. It is the bug you get any time you cache a reference point and then compare live state against it, while some other actor can move the underlying value out of band. Rate limiters that store a start count and get reset administratively. Disk-usage alarms that baseline at boot and then someone mounts a volume. Progress bars that compute percentage against a total that changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;You cannot stop the value moving. You can detect the out-of-band change and shift the baseline by the same amount, so the difference keeps measuring what you meant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;detectOutOfBandChange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// +500 deposit, -500 withdrawal&lt;/span&gt;

&lt;span class="n"&gt;dayStartEquity&lt;/span&gt;  &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;peakEquity&lt;/span&gt;      &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;initialBalance&lt;/span&gt;  &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// every baseline, not just the obvious one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case the platform books transfers as a distinct record type, so detection is a history scan for that type. Concretely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_TYPE&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DEAL_TYPE_BALANCE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DEAL_TYPE_CREDIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_PROFIT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shift the baselines and the measured result is unchanged by the transfer, which is the behaviour you wanted all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that made it production-ready
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Only look when something moved.&lt;/strong&gt; Scanning history on every tick is wasteful. The balance only changes when a trade closes or a transfer lands, so that change is the trigger; otherwise skip the scan entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bound the scan.&lt;/strong&gt; Querying the full account history gets slow on an old account. Anything before today is already baked into the baseline by definition, so the scan only ever covers the current day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn it off in simulation.&lt;/strong&gt; Backtests have no cash transfers, and a per-close history scan across thousands of records turns an optimisation run into a coffee break.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I nearly got wrong
&lt;/h2&gt;

&lt;p&gt;My first instinct was to correct &lt;em&gt;everything&lt;/em&gt; that reads the balance. That would have been a second bug.&lt;/p&gt;

&lt;p&gt;Position sizing is a percentage of the balance. If half the account is withdrawn, the amount you can afford to risk genuinely should halve. That is not distortion, it is the correct response to having less money. Only the &lt;strong&gt;risk baselines&lt;/strong&gt; needed shifting — the things measuring change over time. Anything measuring current capacity was already right.&lt;/p&gt;

&lt;p&gt;Worth asking of any fix like this: which of these values is measuring &lt;em&gt;change&lt;/em&gt;, and which is measuring &lt;em&gt;state&lt;/em&gt;? Only the first kind needs the adjustment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one that will catch you
&lt;/h2&gt;

&lt;p&gt;The program persists its baselines so a restart mid-day does not reset them. So: transfer money while it is stopped, start it again, and it faithfully restores a baseline from before the transfer — and trips instantly on a fix that is otherwise working perfectly.&lt;/p&gt;

&lt;p&gt;Reconciliation has to happen on startup as well as during the run. I only found this by reasoning through the restart path, not by testing, which is its own small lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it
&lt;/h2&gt;

&lt;p&gt;Point the thing at a demo account, move a small amount of money out, and confirm the day's measured result does not budge.&lt;/p&gt;

&lt;p&gt;Two minutes. I had never once run it, and neither, I suspect, has almost anyone shipping this category of software.&lt;/p&gt;




&lt;p&gt;The MQL5 implementation is on GitHub under MIT if it is useful: &lt;a href="https://github.com/mcgary6567-lab/mql5-balance-operations-guard" rel="noopener noreferrer"&gt;mql5-balance-operations-guard&lt;/a&gt;. I write about building automated trading systems at &lt;a href="https://goldscalpers.com" rel="noopener noreferrer"&gt;goldscalpers.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>programming</category>
      <category>lessonslearned</category>
      <category>fintech</category>
    </item>
  </channel>
</rss>
