<?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: FxSocket</title>
    <description>The latest articles on DEV Community by FxSocket (fxsocket).</description>
    <link>https://dev.to/fxsocket</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%2Forganization%2Fprofile_image%2F14750%2Fb77b49a0-795e-44ce-b882-7990dbf2440b.png</url>
      <title>DEV Community: FxSocket</title>
      <link>https://dev.to/fxsocket</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fxsocket"/>
    <language>en</language>
    <item>
      <title>Your trading strategy is probably luck, and here is the math</title>
      <dc:creator>0xdev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:02:15 +0000</pubDate>
      <link>https://dev.to/fxsocket/your-trading-strategy-is-probably-luck-and-here-is-the-math-3nkk</link>
      <guid>https://dev.to/fxsocket/your-trading-strategy-is-probably-luck-and-here-is-the-math-3nkk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t3wupzweopgu6q92z87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t3wupzweopgu6q92z87.png" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;br&gt;
Here is a trading record. 238 closed trades over about four months. Win rate 54.6%. Profit factor 0.99. The equity curve climbs to +$455 by early May, gives it back over the next seven weeks, and claws most of it out again in July before ending where it started.&lt;/p&gt;

&lt;p&gt;It looks like a strategy having an ordinary year. It is 238 draws from a distribution whose true mean I set to roughly zero before I generated it.&lt;/p&gt;

&lt;p&gt;I built a free tool that reads MetaTrader statements and says that out loud, and this post is mostly about the statistics it runs rather than the tool. The numbers in it come from synthetic data I generated specifically so I would know the right answer in advance, which is the only way to test something like this honestly.&lt;/p&gt;

&lt;p&gt;Disclosure: the tool is free and needs no account, and it sits on the site of a paid product I run. Links at the bottom. The math is the point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trading is unusually good at fooling you
&lt;/h2&gt;

&lt;p&gt;Most fields where people look for an edge give you fast, cheap feedback. Trading gives you feedback that is slow, expensive, and dominated by noise. Three things combine badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The signal is tiny next to the variance.&lt;/strong&gt; A decent retail strategy might make an average of $5 per trade with a standard deviation of $50 per trade. That ratio, not the size of the edge, decides how long it takes to see anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cumulative sums of noise look like skill.&lt;/strong&gt; This is the one that does the real damage. Add up fair coin flips, plot the running total, and you get something with trends, plateaus, drawdowns and recoveries. Over 500 flips the total typically wanders about 22 steps from zero in one direction or the other, because the spread of a random walk grows with the square root of its length. Every visual feature a trader reads as structure is produced in abundance by nothing at all.&lt;/p&gt;

&lt;p&gt;An equity curve is exactly that cumulative sum. It is the most reached-for chart in trading and the most misleading, because it converts independent draws into a shape, and humans are extremely good at reading shapes and terrible at reading variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And then you only ever see the survivors.&lt;/strong&gt; Take ten thousand people trading with no edge whatsoever. Their results centre on zero, so about sixteen hundred of them finish more than one standard deviation up. Those sixteen hundred have genuinely profitable records of real trades with real money. Some of them will conclude they have found something. Some of them will sell you a course about it. Nothing in their statement distinguishes them from the eight thousand four hundred others, because there is nothing to distinguish.&lt;/p&gt;

&lt;p&gt;Statistics is not a nice-to-have on top of trading. It is the only thing standing between a trader and a convincing story.&lt;/p&gt;
&lt;h2&gt;
  
  
  How many trades you actually need
&lt;/h2&gt;

&lt;p&gt;The quantity that matters is the standard error of the mean: the standard deviation of your per-trade results divided by the square root of how many you have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&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;b&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;a&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;results&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&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;b&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;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sd&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variance&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;standardError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sd&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the $5 average and $50 standard deviation from earlier and watch what the sample size does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trades&lt;/th&gt;
&lt;th&gt;Standard error&lt;/th&gt;
&lt;th&gt;Mean in standard errors&lt;/th&gt;
&lt;th&gt;Roughly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;one run in three looks this good by chance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;one in twenty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;$1.67&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;one in four hundred&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A useful rule falls out of that. To resolve an edge, you need about &lt;code&gt;(2 * sd / mean) ** 2&lt;/code&gt; trades before the result sits two standard errors from zero. At a ratio of ten to one, that is four hundred trades. Most people form a strong opinion about a strategy somewhere around trade forty.&lt;/p&gt;

&lt;p&gt;This is not a counsel of despair, it is a calibration. Forty trades is fine for finding out whether your execution works, whether your risk sizing is survivable, whether you can follow your own rules. It is not enough to learn whether the strategy makes money, and treating it as though it were is how people scale up right before the mean reverts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Win rate and profit factor will not save you
&lt;/h2&gt;

&lt;p&gt;Both are real numbers and both are quoted constantly, and neither answers the question on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Win rate&lt;/strong&gt; says nothing without the payoff ratio beside it. Ninety percent winners at a one to twenty payoff is a losing system that feels wonderful ninety percent of the time. Ten percent winners at fifty to one is a business. Win rate is the most psychologically available number in trading and close to the least informative, which is an unfortunate combination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Profit factor&lt;/strong&gt; (gross won divided by gross lost) does combine the two, but it inherits the whole sample size problem. A profit factor of 1.5 over forty trades is a number with an enormous error bar attached, and nobody ever prints the error bar.&lt;/p&gt;

&lt;p&gt;Expectancy, the plain average result per trade, is the one that feeds directly into the test that follows. It is also the one nobody quotes, because it is usually a small and unexciting number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the edge
&lt;/h2&gt;

&lt;p&gt;The question "is this edge real" has a standard form: how often would pure chance produce an average at least this far from zero, in a sample this size? That is a two-sided t-test, and on a few hundred trades the normal approximation is close enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Abramowitz &amp;amp; Stegun 7.1.26 is plenty for a p-value rounded to 3 places.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;erf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&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;sign&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&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="mi"&gt;1&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;z&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;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3275911&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;z&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="p"&gt;((((&lt;/span&gt;&lt;span class="mf"&gt;1.061405429&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.453152027&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.421413741&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.284496736&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
      &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="mf"&gt;0.254829592&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
      &lt;span class="nx"&gt;t&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;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;z&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;sign&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tStat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;standardError&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;pValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;erf&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;tStat&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="nx"&gt;SQRT2&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the record I opened with, the average trade is -$0.19 and the p-value comes back at 0.93. Ninety-three percent of random runs that size look at least that good. Correct answer, since I built it with no edge.&lt;/p&gt;

&lt;p&gt;Two things this test does not do, and both matter more than the test itself.&lt;/p&gt;

&lt;p&gt;It does not prove the absence of an edge. A high p-value on ninety trades means you have not measured anything yet, not that there is nothing there. Absence of evidence, in a regime engineered to produce very little evidence.&lt;/p&gt;

&lt;p&gt;And it is not valid if the record you are testing was &lt;em&gt;selected&lt;/em&gt; for looking good. If you tried fifty parameter sets and kept the best, the winner's p-value is meaningless, because you ran fifty tests and reported one. That is the multiple comparisons problem, and backtest overfitting is its natural habitat. The same applies to the trader who shows you their best account out of four. The test assumes you picked the sample before you saw the result, and in trading that assumption is broken more often than it holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sequence luck
&lt;/h2&gt;

&lt;p&gt;Here is the part that changes how people think about their own drawdowns.&lt;/p&gt;

&lt;p&gt;Your equity curve is one ordering of your trades. The trades are what your strategy produced, but the order is an accident. Reshuffle them and the path changes completely while the endpoint stays exactly the same. Draw them with replacement instead of just reshuffling and you also get a distribution of endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paths&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="nx"&gt;horizon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Float64Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&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;maxDrawdowns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Float64Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&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;p&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;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;p&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;let&lt;/span&gt; &lt;span class="nx"&gt;cum&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;peak&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;worst&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;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="nx"&gt;horizon&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="nx"&gt;cum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;results&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;results&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="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;cum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cum&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;peak&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;worst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;worst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;finals&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;=&lt;/span&gt; &lt;span class="nx"&gt;cum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;maxDrawdowns&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;=&lt;/span&gt; &lt;span class="nx"&gt;worst&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;finals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;maxDrawdowns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxDrawdowns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&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;Thirty lines, and it answers a question no single equity curve can. Run it on that same record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The worst drawdown the account actually lived through: $534&lt;/li&gt;
&lt;li&gt;The median worst drawdown across five thousand reorderings: $602&lt;/li&gt;
&lt;li&gt;The ninety-fifth percentile: $1,225&lt;/li&gt;
&lt;li&gt;Share of reorderings that drew down deeper than the real account ever did: 60%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sixty percent. The drawdown this trader survived, and would quite reasonably use to size their risk, is &lt;em&gt;shallower than a coin flip&lt;/em&gt; among the paths their own trades can produce. The number to plan around is $1,225, and they have never seen anything like it.&lt;/p&gt;

&lt;p&gt;This generalises. People calibrate risk against their lived experience, and lived experience is a single draw. Position sizing built on the drawdown you happen to have had is built on the part of the record that is pure sequence luck. The bootstrap is the cheapest possible correction for that, and it needs nothing you do not already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs move the mean and leave the variance alone
&lt;/h2&gt;

&lt;p&gt;This is why costs are so much more dangerous than they look.&lt;/p&gt;

&lt;p&gt;Spread, commission and swap subtract a near-constant amount from every trade. They shift the mean down and barely touch the standard deviation. So their effect is completely invisible in the noise, trade by trade, and absolutely decisive in the only number that compounds.&lt;/p&gt;

&lt;p&gt;The record I have been using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average trade before costs: &lt;strong&gt;+$0.92&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Commission and swap: &lt;strong&gt;-$1.11&lt;/strong&gt; per trade&lt;/li&gt;
&lt;li&gt;Average trade after costs: &lt;strong&gt;-$0.19&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a real gross edge in there. It is smaller than the cost of trading. Nothing in the entry logic is broken, and no amount of work on the entry logic fixes it; the fixes are a cheaper broker, fewer trades, or longer holds. A trader looking at a flat equity curve will almost always go hunting in the signal, because the signal is the interesting part. The answer was in the fee column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the result comes from
&lt;/h2&gt;

&lt;p&gt;Totals hide structure. The same record, bucketed by the hour the trade was opened, puts -$330 in the small hours and +$234 in the afternoon. Bucketed by weekday, Tuesday makes $309 and Friday loses $291.&lt;/p&gt;

&lt;p&gt;Usual caveat, with feeling: these are the same small samples sliced thinner, so each bucket has a wider error bar than the total. Slicing twenty ways and reporting the worst slice is the multiple comparisons problem again, wearing a different hat. Buckets are for generating a hypothesis you then test on data you have not looked at yet, not for concluding that you should stop trading Fridays.&lt;/p&gt;

&lt;p&gt;They are still worth computing, because a bucket that is both large and lopsided is the one kind of finding that survives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing MetaTrader's exports
&lt;/h2&gt;

&lt;p&gt;Enough statistics. The input to all of this is the HTML statement MetaTrader writes, and it is a more interesting parsing target than it has any right to be.&lt;/p&gt;

&lt;p&gt;There are two layouts. MT4 writes a &lt;em&gt;Closed Transactions&lt;/em&gt; table keyed by Ticket and Item. MT5 writes a &lt;em&gt;Positions&lt;/em&gt; section keyed by Position and Symbol, and then appends Orders and Deals sections after it. Rather than sniff the platform first, I scan every row in the document, recognise either header by its column names, and read the rows that follow until the shape stops matching.&lt;/p&gt;

&lt;p&gt;Four things bit me, two of them only after I built realistic fixtures and compared the two formats against each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Column names repeat.&lt;/strong&gt; MT5's Positions header is &lt;code&gt;Time, Position, Symbol, Type, Volume, Price, S/L, T/P, Time, Price, Commission, Swap, Profit&lt;/code&gt;. Both &lt;code&gt;Time&lt;/code&gt; and &lt;code&gt;Price&lt;/code&gt; appear twice, once for the open and once for the close, distinguished by nothing but document order. So the column map takes explicitly named columns first (&lt;code&gt;Open Time&lt;/code&gt;, &lt;code&gt;Close Time&lt;/code&gt; in MT4) and falls back to positional order for the duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Balance rows live inside the trade table.&lt;/strong&gt; MT4 puts deposits and withdrawals in the middle of the closed transactions table, using &lt;code&gt;colspan&lt;/code&gt; so the row has five cells where the trades have fourteen. My first version treated a column count mismatch as the end of the section, which meant a single deposit row at the top silently truncated the entire statement to zero trades. A mismatch has to mean "skip this row", never "this section ended".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But something still has to end a section.&lt;/strong&gt; MT5's Deals header is fourteen columns where Positions is thirteen, within the tolerance I had just widened. Without a guard, every closed trade gets counted a second time from the deals log, with the columns shifted. The fix is that only a row that &lt;em&gt;looks like a header&lt;/em&gt; ends a section, whether or not a usable column map comes out of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;looksLikeHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;)&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;hits&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;for &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;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;cells&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;HEADER_WORDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;++&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;hits&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;&lt;strong&gt;Numbers and dates are localised.&lt;/strong&gt; Statements come out in the terminal's codepage, frequently not UTF-8, so decoding tries UTF-8 first and falls back to windows-1252 when the result is full of replacement characters. Thousands separators may be spaces, including non-breaking ones, and a localised build can emit a decimal comma. The rule that handles every case I found: whichever separator sits last and leaves one to three trailing digits is the decimal one.&lt;/p&gt;

&lt;p&gt;Timestamps are their own trap. They are printed in broker server time with no offset, so I build them with &lt;code&gt;Date.UTC&lt;/code&gt; from the printed components and read the hour back with &lt;code&gt;getUTCHours&lt;/code&gt;. That way the hour-of-day buckets show the clock the trader actually saw, rather than silently shifting by the visitor's timezone. When the same data comes from an API as real instants instead, the chart says so on the axis. Two different clocks quietly mixed into one histogram is a bug that produces plausible output forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser only, and the one exception
&lt;/h2&gt;

&lt;p&gt;Asking strangers to upload their complete trading history to your server is a lot to ask, and storing it is worse. So the upload path does not have a server. The file is read with the browser's own file reader, parsed with &lt;code&gt;DOMParser&lt;/code&gt;, and analysed in the tab. Nothing is transmitted, which means the page can say nothing is transmitted without any asterisks.&lt;/p&gt;

&lt;p&gt;That constraint turned out to shape the whole thing. The statistics, the bootstrap, the charts all had to be cheap enough to run on a phone, which ruled out anything heavy and ruled out a charting dependency. Five thousand bootstrap paths over a few hundred trades is a couple of million additions, which is tens of milliseconds, so the limit was never the math.&lt;/p&gt;

&lt;p&gt;There is one exception. The tool can also pull closed positions from a trading API instead of a file, and that API serves no CORS headers, so a browser cannot call it. That path goes through a small relay that forwards the key to two read-only endpoints and neither logs nor stores it. I would rather have had no server at all, but the honest version of "we need a relay and here is exactly what it does" beats pretending the constraint does not exist.&lt;/p&gt;

&lt;p&gt;The charts ended up as hand-written SVG for the same reason: no dependency, and the whole tool compiles into a single 42 KB route chunk that one of the site's forty-one pages loads. Everything else on the site is untouched by it, which I check at build time rather than hope for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the math assumes, and where it breaks
&lt;/h2&gt;

&lt;p&gt;Every technique above rests on two assumptions, and both are breakable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independence.&lt;/strong&gt; The t-test and the bootstrap both assume each trade is a separate draw. Grid and martingale systems violate this flagrantly: their trades are deliberately correlated, which is the entire mechanism, and resampling them scatters a structure that was load-bearing. Overlapping positions in correlated instruments break it more quietly. When independence fails, the test overstates its own confidence, so the p-values look better than they deserve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stationarity.&lt;/strong&gt; Resampling your past trades assumes the future draws from the same distribution. Markets change regime, spreads widen, a broker changes execution. The bootstrap tells you what your trades imply about a world that keeps behaving like the one they came from, which is a narrower claim than it looks.&lt;/p&gt;

&lt;p&gt;And the big one, again, because it is the one that gets everybody: none of this is valid on a record that was selected for looking good. Test the strategy you committed to, not the best of the ones you tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The tool is at &lt;a href="https://fxsocket.com/tools/statement-analyzer" rel="noopener noreferrer"&gt;fxsocket.com/tools/statement-analyzer&lt;/a&gt;. Drop in an MT4 or MT5 statement and it runs everything above: expectancy with the test against zero, profit factor, drawdown, the cost breakdown, the buckets, and the bootstrap with the drawdown percentiles. Free, no account, and the file stays in your browser.&lt;/p&gt;

&lt;p&gt;If you do not trade MetaTrader, the pieces in this post are a hundred lines of plain JavaScript and port to whatever holds your trade log. The standard error calculation alone would change how a lot of people read their own records.&lt;/p&gt;

&lt;p&gt;I would genuinely like to be argued with on two points. First, whether the bootstrap should resample trades or blocks of trades, since block resampling preserves some autocorrelation and I decided the added complexity was not worth it for most retail records. Second, whether a p-value is the right thing to show a trader at all, or whether a confidence interval on expectancy would land better. Comments are open.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trading leveraged products carries a real risk of loss. Nothing in this post is financial advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>showdev</category>
      <category>statistics</category>
      <category>api</category>
    </item>
    <item>
      <title>How We put MetaTrader 4 and 5 behind a REST and WebSocket API</title>
      <dc:creator>0xdev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:39:15 +0000</pubDate>
      <link>https://dev.to/fxsocket/how-we-put-metatrader-4-and-5-behind-a-rest-and-websocket-api-kan</link>
      <guid>https://dev.to/fxsocket/how-we-put-metatrader-4-and-5-behind-a-rest-and-websocket-api-kan</guid>
      <description>&lt;p&gt;Most retail forex and CFD brokers still hand you a MetaTrader 4 or 5 login, and MetaTrader is surprisingly hard to automate from normal code. Your options are roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write MQL and live inside the terminal,&lt;/li&gt;
&lt;li&gt;use the official &lt;code&gt;MetaTrader5&lt;/code&gt; Python package, which only covers MT5 and needs a Windows machine with the terminal installed and running next to your script,&lt;/li&gt;
&lt;li&gt;or glue something together yourself with an Expert Advisor, files and sockets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever you pick, you end up keeping Windows machines running around the clock, with a terminal logged in for every account you trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fxsocket.com" rel="noopener noreferrer"&gt;FxSocket&lt;/a&gt; is what I built instead. You give it a broker login, it starts a dedicated MT4 or MT5 terminal for that account in the cloud, and you get a REST and WebSocket API back. It works with any broker that offers MT4 or MT5, on demo and live accounts, from any language that can make an HTTP request.&lt;/p&gt;

&lt;p&gt;Disclosure up front: I built this, and it's a paid product. I've tried to make the post useful even if you never sign up. After the code samples, most of it is about the engineering: API rules that came from worrying about double fills, and how MetaTrader runs headless on Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A dedicated terminal per account. There's no shared gateway multiplexing everyone's logins; your requests run inside your own MetaTrader instance.&lt;/li&gt;
&lt;li&gt;REST endpoints for balance and equity, open positions and pending orders, deal and position history, symbols, quotes, OHLC history and contract specs, plus order send, modify and close, and margin and profit calculators.&lt;/li&gt;
&lt;li&gt;One WebSocket per account with six topics: ticks, bar closes, account, positions, trade events and terminal status.&lt;/li&gt;
&lt;li&gt;A management API (&lt;code&gt;/v1&lt;/code&gt;) to link and unlink accounts, send one order to many accounts at once, and create read-only keys scoped to specific accounts.&lt;/li&gt;
&lt;li&gt;Official SDKs for Python (sync and async, Pydantic models) and Node/TypeScript (ESM and CommonJS), both MIT licensed.&lt;/li&gt;
&lt;li&gt;An OpenAPI 3 spec and Swagger UI on every account, so you can generate a client for Go, Rust, C# or anything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FxSocket is not a broker. Your money stays with your broker, and FxSocket has no way to deposit, withdraw or move it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From zero to a first order with curl
&lt;/h2&gt;

&lt;p&gt;Grab an API key from the &lt;a href="https://app.fxsocket.com" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt;, then link an account. &lt;code&gt;server&lt;/code&gt; is the broker server name exactly as MetaTrader shows it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FXSOCKET_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fxs_live_...

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.fxsocket.com/v1/accounts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FXSOCKET_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"platform": "mt5", "server": "VTMarkets-Demo", "login": 1234567, "password": "..."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The password is stored encrypted and never returned by the API. (An investor password is enough for read-only access; placing trades needs the master password.) The response, trimmed:&lt;br&gt;
&lt;/p&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b5236acf-c39b-4e8b-be64-a96afb7e6086"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mt5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VTMarkets-Demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234567&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connecting"&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;p&gt;&lt;code&gt;connecting&lt;/code&gt; means a real terminal is booting and logging in to your broker. Poll &lt;code&gt;GET /v1/accounts/{id}&lt;/code&gt; until it reads &lt;code&gt;connected&lt;/code&gt;, which usually takes a few minutes.&lt;/p&gt;

&lt;p&gt;Every per-account route hangs off &lt;code&gt;/mt5/{account_id}&lt;/code&gt; (MT4 accounts use &lt;code&gt;/mt4/{account_id}&lt;/code&gt; with the same routes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;b5236acf-c39b-4e8b-be64-a96afb7e6086

curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FXSOCKET_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.fxsocket.com/mt5/&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;/AccountSummary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9686.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.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;"profit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.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;"equity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9686.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"margin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.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;"freeMargin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9686.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"marginLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.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;"leverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo"&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;p&gt;A market buy with a stop loss and take profit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.fxsocket.com/mt5/&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;/OrderSend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FXSOCKET_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"symbol": "EURUSD", "operation": "Buy", "volume": 0.1, "stopLoss": 1.148, "takeProfit": 1.162}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retcode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retcodeDescription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;211438760&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;211438761&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.15333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.15325&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.15333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&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;p&gt;Internalize this one early: an HTTP 200 doesn't mean the order filled. It means MetaTrader answered. Check &lt;code&gt;success&lt;/code&gt; and &lt;code&gt;retcode&lt;/code&gt; (&lt;code&gt;10009&lt;/code&gt; is done, &lt;code&gt;10008&lt;/code&gt; is placed). That distinction ended up shaping a lot of the API (more on that below).&lt;/p&gt;

&lt;p&gt;Pending orders use the same route with &lt;code&gt;BuyLimit&lt;/code&gt;, &lt;code&gt;SellStop&lt;/code&gt; and friends, plus a &lt;code&gt;price&lt;/code&gt;. &lt;code&gt;OrderModify&lt;/code&gt; changes SL/TP (or a pending order's price), and &lt;code&gt;OrderClose&lt;/code&gt; closes by ticket, with an optional &lt;code&gt;volume&lt;/code&gt; for a partial close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming over WebSocket
&lt;/h2&gt;

&lt;p&gt;Each account has one WebSocket. Browsers can't set custom headers on a WebSocket upgrade, so the key goes in the query string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ wscat -c "wss://api.fxsocket.com/mt5/$ACCOUNT/ws?api_key=$FXSOCKET_API_KEY"
&amp;gt; {"action": "subscribe", "topic": "prices", "symbol": "EURUSD"}
&amp;lt; {"type": "tick", "symbol": "EURUSD", "data": {"bid": 1.15325, "ask": 1.15333, "last": 0.0, "volume": 0, "time": "2026-06-11T08:55:56.728Z"}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Topics are &lt;code&gt;prices&lt;/code&gt;, &lt;code&gt;bars&lt;/code&gt; (per symbol and timeframe), &lt;code&gt;account&lt;/code&gt;, &lt;code&gt;positions&lt;/code&gt;, &lt;code&gt;trades&lt;/code&gt; and &lt;code&gt;terminal&lt;/code&gt;. When you subscribe you get a snapshot first, then deltas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fxsocket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fxsocket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# reads FXSOCKET_API_KEY
&lt;/span&gt;    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;equity:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account_summary&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;equity&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EURUSD ask:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EURUSD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EURUSD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stop_loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;take_profit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.162&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_modify&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;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;take_profit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.165&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fx.terminal(account)&lt;/code&gt; looks up where that account's terminal lives, so the same code works on the shared cluster and on a private server (more on those later).&lt;/p&gt;

&lt;p&gt;Streaming is async-first. A dropped connection reconnects on its own and replays your subscriptions:&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;asyncio&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fxsocket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AccountUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tick&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&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;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe_prices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EURUSD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe_account&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Tick&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;event&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&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="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;AccountUpdate&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;equity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;equity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Node / TypeScript SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @fxsocket/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FxSocket&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fxsocket/sdk&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;fx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FxSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// reads FXSOCKET_API_KEY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&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;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tick&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="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trade&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="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;net&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netProfit&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribePrices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EURUSD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeTrades&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stream events are a discriminated union on &lt;code&gt;type&lt;/code&gt;, so TypeScript narrows &lt;code&gt;data&lt;/code&gt; for you. The stream is also an async iterable if you'd rather use &lt;code&gt;for await&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One order, many accounts
&lt;/h2&gt;

&lt;p&gt;Once you run more than a couple of accounts (copy trading, a stack of prop firm challenges, one strategy spread across brokers), you want one request instead of N. &lt;code&gt;POST /v1/orders&lt;/code&gt; fans an order out to several terminals concurrently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.fxsocket.com/v1/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FXSOCKET_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Idempotency-Key: signal-4711"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "defaults": {"operation": "buy", "symbol": "EURUSD", "stop_loss": 1.148},
    "orders": [
      {"account_id": "6fcb1e23-...", "volume": 0.10},
      {"account_id": "a91b0d47-...", "volume": 0.25, "symbol": "EURUSD.sd"},
      {"account_id": "c73d55e2-...", "volume": 0.05, "symbol": "EURUSDm"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each leg inherits &lt;code&gt;defaults&lt;/code&gt; and can override any field. The per-leg &lt;code&gt;symbol&lt;/code&gt; is there for a boring but very real reason: brokers spell instruments differently. &lt;code&gt;EURUSD&lt;/code&gt;, &lt;code&gt;EURUSD.sd&lt;/code&gt; and &lt;code&gt;EURUSDm&lt;/code&gt; are the same pair at three different brokers, so one symbol across mixed brokers will partly fail by design.&lt;/p&gt;

&lt;p&gt;The response has one result per leg, in order, and the statuses are where it gets interesting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;status&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Executed?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The broker accepted it&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rejected&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The broker refused it (see &lt;code&gt;retcode&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invalid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The terminal refused the request itself&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unavailable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The terminal is up but not ready to trade&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unreachable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;There's no terminal to talk to&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The deadline ran out&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unknown&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Validation is all-or-nothing: one malformed leg rejects the whole batch before anything is sent. Execution isn't. A partial fill across brokers is normal rather than an error, so you get a &lt;code&gt;200&lt;/code&gt; with a result for every leg.&lt;/p&gt;

&lt;p&gt;Closing works by intent rather than by ticket. &lt;code&gt;POST /v1/orders/close&lt;/code&gt; takes a selector like &lt;code&gt;{"symbol": "EURUSD", "side": "long"}&lt;/code&gt;, looks up matching positions on each account and closes them. You don't have to fetch tickets first from a list that may already be stale by the time you act on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons from designing a trading API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A timeout is not a failure
&lt;/h3&gt;

&lt;p&gt;The most important row in that table is the last one. When a request to a broker times out, the order may well have gone through. If your client calls that a failure and retries, you've just doubled your position. That's why &lt;code&gt;timeout&lt;/code&gt; is reported as unknown, never as failed.&lt;/p&gt;

&lt;p&gt;The batch endpoints take an &lt;code&gt;Idempotency-Key&lt;/code&gt; header for the same reason. Replay the identical body with the same key within 15 minutes and you get the original response back, with nothing sent. The same key with a different body gets a &lt;code&gt;422&lt;/code&gt;, and the same key while the first batch is still running gets a &lt;code&gt;409&lt;/code&gt;. If the idempotency store itself is down, the request is refused with a &lt;code&gt;503&lt;/code&gt; rather than traded without the guarantee.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the dangerous thing hard to do by accident
&lt;/h3&gt;

&lt;p&gt;A few rules that came from imagining the worst possible afternoon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A close needs a symbol, and "everything" has to be typed. An omitted &lt;code&gt;symbol&lt;/code&gt; is a &lt;code&gt;400&lt;/code&gt;, never a silent close-all. If you mean every symbol, you send &lt;code&gt;"*"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A close only touches open positions by default, so a routine close doesn't also delete your resting pending orders.&lt;/li&gt;
&lt;li&gt;Unknown fields are rejected. &lt;code&gt;/v1&lt;/code&gt; uses snake_case and the terminal routes use camelCase. If someone pastes &lt;code&gt;stopLoss&lt;/code&gt; into a &lt;code&gt;/v1&lt;/code&gt; body and it gets silently ignored, they've just opened a position with no stop. So it's a &lt;code&gt;400&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;The SDKs refuse a stop loss of &lt;code&gt;0&lt;/code&gt; on a modify. MetaTrader reads a zero stop loss as "remove the stop", so the SDK makes you ask for that explicitly with &lt;code&gt;clear_stop_loss=True&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  "Done" has more than one shape
&lt;/h3&gt;

&lt;p&gt;MetaTrader returns a result code for every trade request, and the SDKs classify each one as &lt;code&gt;applied&lt;/code&gt;, &lt;code&gt;no_change&lt;/code&gt;, &lt;code&gt;partial&lt;/code&gt; or &lt;code&gt;rejected&lt;/code&gt;. &lt;code&gt;no_change&lt;/code&gt; (retcode &lt;code&gt;10025&lt;/code&gt;, meaning the SL/TP you asked for is already set) comes back with &lt;code&gt;success: false&lt;/code&gt;, yet it's exactly what you want when you re-send a modification after losing the first response. So results also carry &lt;code&gt;is_effective&lt;/code&gt;, which is true for both &lt;code&gt;applied&lt;/code&gt; and &lt;code&gt;no_change&lt;/code&gt; and makes SL/TP updates safe to retry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always bound your history requests
&lt;/h3&gt;

&lt;p&gt;On MT5, ask for price history without a date range and the terminal asks the broker for the entire history of that timeframe. On a typical demo account that's around a million M1 bars, which blows straight through the request deadline. Pass a &lt;code&gt;from&lt;/code&gt; date and the same call answers in well under a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood: MetaTrader on Kubernetes
&lt;/h2&gt;

&lt;p&gt;MetaTrader is a Windows desktop application. There's no headless mode, and the official ways in are MQL running inside the terminal or (for MT5) a Python package that talks to a terminal on the same Windows machine. Here's how it runs as a service anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  One terminal per account, in a container
&lt;/h3&gt;

&lt;p&gt;On the shared cluster, every linked account gets its own pod running the real MetaTrader terminal under Wine, with Xvfb standing in for a display.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Rust DLL inside the terminal
&lt;/h3&gt;

&lt;p&gt;The bridge is a Rust library loaded into the terminal process. It runs a tokio runtime with an axum server for REST and WebSocket. MQL programs inside the terminal drain a command queue on the terminal's main thread, call MetaTrader's own functions, and pass the results back across the FFI boundary. The OpenAPI spec is generated from the Rust types with utoipa, which is how every account gets an accurate Swagger UI for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  MQL5 has no "attach this Expert Advisor" function
&lt;/h3&gt;

&lt;p&gt;This one surprised me. To start the trading EA, the bridge opens a chart and applies a chart template that has the EA on it. Streaming uses the same trick: the first subscriber to a symbol's ticks spawns a small event EA on a chart for that symbol, and when the last subscriber leaves, the chart closes. Events fan out through a tokio broadcast channel, and a slow WebSocket client gets dropped messages plus a &lt;code&gt;warning&lt;/code&gt; frame instead of stalling everyone else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never run two terminals on one login
&lt;/h3&gt;

&lt;p&gt;Brokers treat two concurrent sessions on the same account as a duplicate login and kick one of them, and trade state gets murky. A normal Kubernetes rolling update briefly runs the old and new pod side by side, which is exactly that situation. So terminal Deployments use the &lt;code&gt;Recreate&lt;/code&gt; strategy. The cost is a short gap during image rollouts, and I'll take that trade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Health checks can't depend on the broker
&lt;/h3&gt;

&lt;p&gt;The terminal's own health route needs a live broker session. If the liveness probe used it, a terminal that's simply mid-login (or a broker having a bad minute) would get restarted in a loop. So the Kubernetes probes only check that the port is open, and a reconcile loop checks the real thing every couple of minutes: terminal alive, broker connected, account logged in. The same loop creates missing terminals, heals half-created ones and sweeps up orphans, so a half-finished teardown never leaves broker credentials lying around.&lt;/p&gt;

&lt;h3&gt;
  
  
  Config changes roll the pod on purpose
&lt;/h3&gt;

&lt;p&gt;A terminal reads its config once, at boot. The pod template carries a hash of that config (credentials, keys, image), so rotating an API key changes the hash, and the next reconcile pass restarts the terminal with the new key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broker discovery is its own project
&lt;/h3&gt;

&lt;p&gt;A fresh MT5 install can't resolve arbitrary broker server names. Without that broker's &lt;code&gt;servers.dat&lt;/code&gt;, the terminal boots, the API answers, and it never even attempts a login. You just get an account summary full of zeros. So a separate broker search service keeps an index of more than 9,000 broker servers, and each container installs the right file before the terminal starts.&lt;/p&gt;

&lt;p&gt;Related lesson: a truncated &lt;code&gt;servers.dat&lt;/code&gt; can make the terminal's parser balloon until it OOM-kills a 4 GiB pod in under five seconds. Verify your uploads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth lives in the terminal, not the gateway
&lt;/h3&gt;

&lt;p&gt;The gateway only routes. Each terminal starts with its owner's API key (plus any read-only keys scoped to that account) and checks every request itself. A perfectly valid key for account A, presented at account B's URL, is just a wrong key and gets a &lt;code&gt;401&lt;/code&gt;. There's no permission lookup to misconfigure, because the terminal doesn't know anyone else's keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolation costs memory
&lt;/h3&gt;

&lt;p&gt;Each terminal reserves 1 GiB, even though steady-state usage sits around 300 to 500 MB. One terminal per account is simple to reason about and hard to get wrong, but memory is the real scaling cost of this design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private servers with a fixed IP
&lt;/h2&gt;

&lt;p&gt;Some prop firms want to whitelist the IP your trades come from, and some traders simply want their own box. For that there are private servers: a dedicated server in a region you pick, with a fixed IP that survives reboots, resizes and rebuilds. A new one is live in 5 to 10 minutes and speaks the exact same REST, WebSocket and SDK API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;At the time of writing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starter: €12 per account per month, full REST and WebSocket access, live and demo accounts&lt;/li&gt;
&lt;li&gt;Pro: €549 per month, 60 accounts included&lt;/li&gt;
&lt;li&gt;Premium: €949 per month, 110 accounts included&lt;/li&gt;
&lt;li&gt;Private server: €17 per month for the first account, plus €14 for each additional account on that server, billed separately from your plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can pay by card, or prepay a balance in crypto that covers your accounts as they renew.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://fxsocket.com" rel="noopener noreferrer"&gt;fxsocket.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://fxsocket.com/docs" rel="noopener noreferrer"&gt;fxsocket.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Management API reference (Swagger): &lt;a href="https://api.fxsocket.com/v1/docs" rel="noopener noreferrer"&gt;api.fxsocket.com/v1/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python SDK: &lt;a href="https://pypi.org/project/fxsocket/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; and &lt;a href="https://github.com/fxsocket-com/FxSocket-Python-SDK" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node SDK: &lt;a href="https://www.npmjs.com/package/@fxsocket/sdk" rel="noopener noreferrer"&gt;npm&lt;/a&gt; and &lt;a href="https://github.com/fxsocket-com/FxSocket-Node-SDK" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need a funded account to try any of this. A free demo account from your MT4 or MT5 broker works with every endpoint in this post. cTrader support is on the roadmap.&lt;/p&gt;

&lt;p&gt;If you've automated MetaTrader some other way, I'd like to hear what hurt. And if you think one of the design choices above is wrong, tell me in the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trading leveraged products carries a real risk of loss. Nothing in this post is financial advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>cloud</category>
      <category>software</category>
    </item>
  </channel>
</rss>
