<?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: Edgelog</title>
    <description>The latest articles on DEV Community by Edgelog (@edgelogtrading).</description>
    <link>https://dev.to/edgelogtrading</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%2F4231042%2Fad101fa0-d380-4cf2-adac-663971402d6e.png</url>
      <title>DEV Community: Edgelog</title>
      <link>https://dev.to/edgelogtrading</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edgelogtrading"/>
    <language>en</language>
    <item>
      <title>Trading metrics look simple until you hit these 6 edge cases</title>
      <dc:creator>Edgelog</dc:creator>
      <pubDate>Sat, 07 Nov 2026 10:01:00 +0000</pubDate>
      <link>https://dev.to/edgelogtrading/trading-metrics-look-simple-until-you-hit-these-6-edge-cases-100c</link>
      <guid>https://dev.to/edgelogtrading/trading-metrics-look-simple-until-you-hit-these-6-edge-cases-100c</guid>
      <description>&lt;p&gt;Win rate is wins divided by trades. Profit factor is money won divided by money lost. You can write both in five minutes.&lt;/p&gt;

&lt;p&gt;Then real trade history shows up: fees, trades that close at exactly zero, accounts that never lost, deposits in the middle of a run. We build Edgelog, a free trading journal, and these are the six cases that changed our formulas the most. If you ever build anything that summarizes trades (or any wins and losses), they apply to you too.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Decide what "net" means before anything else
&lt;/h2&gt;

&lt;p&gt;A broker gives you a profit number and a fee number. If you classify wins on the gross number, a trade that made $10 and paid $12 in fees counts as a win. Your trader lost $2.&lt;/p&gt;

&lt;p&gt;We compute everything on one value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tradeNet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;pnl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;fees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pnl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fees&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// fees are a positive cost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Win, loss, totals, averages: all from &lt;code&gt;tradeNet&lt;/code&gt;. One definition, used everywhere, beats a "gross" and a "net" version that slowly drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Zero is its own outcome (and floats make zero hard)
&lt;/h2&gt;

&lt;p&gt;Some trades close at exactly breakeven. If you count them as losses, you punish the trader for a scratch. If you count them as wins, you flatter them.&lt;/p&gt;

&lt;p&gt;So there are three outcomes, and the comparison happens on cents, not raw floats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;win&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;breakeven&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// half away from zero&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;win&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;breakeven&lt;/span&gt;&lt;span class="dl"&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;Without the rounding, &lt;code&gt;0.1 + 0.2 - 0.3&lt;/code&gt; is a tiny positive number and becomes a "win".&lt;/p&gt;

&lt;p&gt;Breakeven trades are left out of win rate (&lt;code&gt;wins / (wins + losses)&lt;/code&gt;), but they stay in the trade count and in expectancy, because they still happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Profit factor with no losing trades is not infinity
&lt;/h2&gt;

&lt;p&gt;Profit factor is gross profit divided by gross loss. A new account with five winning trades has a gross loss of 0, and &lt;code&gt;x / 0&lt;/code&gt; in JavaScript is &lt;code&gt;Infinity&lt;/code&gt;. Show that on a dashboard and people screenshot it.&lt;/p&gt;

&lt;p&gt;We return &lt;code&gt;null&lt;/code&gt; and render "—" with a short note ("No losing trades yet"). With losers but no winners, it's a real 0. Typing it as &lt;code&gt;number | null&lt;/code&gt; forces every screen to handle the empty case on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Expectancy should divide by every trade
&lt;/h2&gt;

&lt;p&gt;Expectancy answers "what does one trade earn me on average?" We use net P&amp;amp;L divided by total trades, breakevens included. Dropping breakevens from the denominator makes the number look better than the trader's real experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Drawdown needs a starting point, and a choice
&lt;/h2&gt;

&lt;p&gt;Max drawdown is the biggest fall from a peak. Two details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The first peak is the starting balance&lt;/strong&gt;, not the first trade. Otherwise a losing first week shows no drawdown at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Percent drawdown is ambiguous.&lt;/strong&gt; The largest dollar drop and the largest percentage drop can happen at different times. We report the percentage at the point of the largest absolute drop, and we documented that choice so nobody "fixes" it into a different number later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Deposits are not profit
&lt;/h2&gt;

&lt;p&gt;Synced accounts often start with a small first deposit, then more money arrives later. If you measure gain against that first deposit, a modest month can show a gain of several hundred percent.&lt;/p&gt;

&lt;p&gt;For synced accounts we use net deposits as the base: current balance minus the net P&amp;amp;L of closed trades. Starting balance plus every trade's net then lands exactly on the broker's balance, which is a nice built-in check.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing: write the definitions down
&lt;/h2&gt;

&lt;p&gt;Every one of these was a bug report before it was a rule. What helped most wasn't clever code. It was keeping a plain document that says, for each metric, the exact formula, what happens with empty input, and which edge cases are deliberate. Tests point at that document, and so do code reviews.&lt;/p&gt;

&lt;p&gt;If you want to see these numbers on your own trades, &lt;a href="https://edgelogtrading.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=posts_nov2026_2" rel="noopener noreferrer"&gt;Edgelog&lt;/a&gt; is free (1 connected account, up to 10,000 stored trades) and syncs MT4/MT5, Binance, Bybit and OKX read-only, or imports a CSV.&lt;/p&gt;

&lt;p&gt;Which metric gave you the most trouble? Share the edge case you hit.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trading involves risk. Edgelog is a journal, not financial advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Syncing MetaTrader trades to a web app: 5 bugs that taught us idempotency</title>
      <dc:creator>Edgelog</dc:creator>
      <pubDate>Fri, 06 Nov 2026 20:25:58 +0000</pubDate>
      <link>https://dev.to/edgelogtrading/syncing-metatrader-trades-to-a-web-app-5-bugs-that-taught-us-idempotency-25om</link>
      <guid>https://dev.to/edgelogtrading/syncing-metatrader-trades-to-a-web-app-5-bugs-that-taught-us-idempotency-25om</guid>
      <description>&lt;p&gt;We build Edgelog, a free trading journal for forex and crypto traders. The feature people care about most is the boring one: you install a small Expert Advisor in MT4 or MT5, and your closed trades show up in the journal without copying anything by hand.&lt;/p&gt;

&lt;p&gt;It sounds simple. A script reads closed deals and POSTs them to an API. In practice it took us a lot of versions to get it right, and almost every bug came back to the same lesson: &lt;strong&gt;assume every message can arrive twice, late, or never.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are the five that hurt the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "Send each trade once" is not a strategy
&lt;/h2&gt;

&lt;p&gt;Our first version sent a deal when MetaTrader told us a trade had closed, and remembered that it had sent it. Then terminals restarted, VPSs lost their connection for an hour, and the same deal got sent again from a fresh session. Or it didn't get sent at all.&lt;/p&gt;

&lt;p&gt;The fix was to stop trying to send things exactly once and make sending twice harmless. Every deal has a ticket number from the broker, so the server stores deals in a ledger keyed by &lt;code&gt;(account, platform, ticket)&lt;/code&gt; and upserts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// simplified&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;live_deals&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dealRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account,platform,ticket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a replay updates a row instead of creating a duplicate trade. Closed trades are rebuilt from that ledger, so the journal is always derived from the same source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Events are hints, history is the truth
&lt;/h2&gt;

&lt;p&gt;MetaTrader gives you trade events, but you can't rely on getting all of them. If the terminal is busy, offline, or your EA is stuck in a slow network call, a close can slip past.&lt;/p&gt;

&lt;p&gt;So the EA now runs a &lt;strong&gt;reconciliation sweep&lt;/strong&gt; every cycle. Instead of trusting what it heard, it re-reads the deal history for the window and works out what the server hasn't accepted yet. Events just make the sweep happen sooner. Because the server side is idempotent (bug #1), the sweep can be pessimistic and re-send without causing harm.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The broker's clock is not your clock
&lt;/h2&gt;

&lt;p&gt;To read history you ask MetaTrader for deals between two times. We used the terminal's last known &lt;strong&gt;server&lt;/strong&gt; time as the upper bound. That value only updates when a price tick arrives. On a quiet market, or a weekend, it goes stale, and a deal that closed after the last tick falls outside the window. It was simply invisible to the sweep.&lt;/p&gt;

&lt;p&gt;The lesson: when you query a time window on someone else's clock, leave a margin and don't assume that clock is current.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. "2.9" is greater than "2.10" (if you compare strings)
&lt;/h2&gt;

&lt;p&gt;We show a banner when someone runs an outdated EA. It never fired. The current version was stored as a string from an older versioning scheme, and real versions like &lt;code&gt;2.10&lt;/code&gt;, &lt;code&gt;2.26&lt;/code&gt; and &lt;code&gt;2.36&lt;/code&gt; were compared as text. As strings, &lt;code&gt;"2.9" &amp;gt; "2.10"&lt;/code&gt;. So nobody was ever "outdated".&lt;/p&gt;

&lt;p&gt;That mattered, because an old EA build was retrying too aggressively and burning serverless CPU. The fix was a boring numeric compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isEaVersionOutdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;y&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="kc"&gt;false&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;We also learned to bump the "minimum version" only after the compiled &lt;code&gt;.ex4&lt;/code&gt;/&lt;code&gt;.ex5&lt;/code&gt; is published. Raise it first, and every installed EA gets rejected while the download link still serves the old build. That's a full outage with no way out for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A quiet market should mean a quiet server
&lt;/h2&gt;

&lt;p&gt;Forex closes for the weekend, but terminals keep running. An EA polling every few seconds, multiplied by every user, is a lot of pointless requests on a serverless bill.&lt;/p&gt;

&lt;p&gt;Two changes fixed most of it. The server acknowledges heartbeat-style posts &lt;strong&gt;before&lt;/strong&gt; doing any database work. And the EA backs its timer off when nothing is happening, then speeds up again on the next trade. We set the backoff once when entering idle, not on every tick, which was its own small bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part we didn't compromise on: read-only
&lt;/h2&gt;

&lt;p&gt;The EA only reads account and deal history. It never places, modifies or closes orders. For a journal that felt like the right line: traders are trusting it on a live account, and a sync tool should not be able to trade.&lt;/p&gt;

&lt;p&gt;Crypto works the same way. Binance, Bybit and OKX connect with read-only API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;Edgelog is free: one connected account and up to 10,000 stored trades, plus CSV import if you don't use MetaTrader. You get a journal, a playbook for your setups, and stats like win rate, profit factor and drawdown by session.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://edgelogtrading.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=posts_nov2026" rel="noopener noreferrer"&gt;edgelogtrading.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built sync for a desktop app that drops connections, we'd like to hear how you handled it. Idempotency keys, sweeps, something else?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trading involves risk. Edgelog is a journal, not financial advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>postgres</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
