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.

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: assume every message can arrive twice, late, or never.

Here are the five that hurt the most.

1. "Send each trade once" is not a strategy

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.

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 (account, platform, ticket) and upserts:

// simplified await db .from('live_deals') .upsert(dealRows, { onConflict: 'account,platform,ticket' }); 

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.

2. Events are hints, history is the truth

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.

So the EA now runs a reconciliation sweep 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.

3. The broker's clock is not your clock

To read history you ask MetaTrader for deals between two times. We used the terminal's last known server 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.

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

4. "2.9" is greater than "2.10" (if you compare strings)

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 2.10, 2.26 and 2.36 were compared as text. As strings, "2.9" > "2.10". So nobody was ever "outdated".

That mattered, because an old EA build was retrying too aggressively and burning serverless CPU. The fix was a boring numeric compare:

export function isEaVersionOutdated(installed: string, current: string): boolean { const parse = (v: string) => v.split('.').map((p) => parseInt(p, 10) || 0); const a = parse(installed); const b = parse(current); for (let i = 0; i < Math.max(a.length, b.length); i++) { const x = a[i] ?? 0; const y = b[i] ?? 0; if (x !== y) return x < y; } return false; } 

We also learned to bump the "minimum version" only after the compiled .ex4/.ex5 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.

5. A quiet market should mean a quiet server

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.

Two changes fixed most of it. The server acknowledges heartbeat-style posts before 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.

The part we didn't compromise on: read-only

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.

Crypto works the same way. Binance, Bybit and OKX connect with read-only API keys.

If you want to try it

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.

👉 edgelogtrading.com

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?

Trading involves risk. Edgelog is a journal, not financial advice.