pmt4api 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pmt4api/__init__.py +32 -0
- pmt4api/__main__.py +78 -0
- pmt4api/_account.py +253 -0
- pmt4api/_base.py +24 -0
- pmt4api/_coverage.py +186 -0
- pmt4api/_events.py +189 -0
- pmt4api/_market.py +263 -0
- pmt4api/_native/CHANGELOG.md +362 -0
- pmt4api/_native/THIRD-PARTY-NOTICES.txt +7 -0
- pmt4api/_native/docs/ERRORS.md +136 -0
- pmt4api/_native/docs/LICENSING.md +55 -0
- pmt4api/_native/docs/SECURITY.md +38 -0
- pmt4api/_native/wmt4api.dll +0 -0
- pmt4api/_risk.py +66 -0
- pmt4api/_runtime.py +154 -0
- pmt4api/_session.py +171 -0
- pmt4api/_streams.py +238 -0
- pmt4api/_trading.py +384 -0
- pmt4api/_util.py +78 -0
- pmt4api/bars.py +62 -0
- pmt4api/client.py +143 -0
- pmt4api/diagnostics.py +38 -0
- pmt4api/docs/API.md +702 -0
- pmt4api/docs/FROM-CSHARP.md +151 -0
- pmt4api/errors.py +233 -0
- pmt4api/frames.py +21 -0
- pmt4api/models.py +337 -0
- pmt4api/py.typed +0 -0
- pmt4api/servers.py +40 -0
- pmt4api-1.0.0.dist-info/METADATA +389 -0
- pmt4api-1.0.0.dist-info/RECORD +33 -0
- pmt4api-1.0.0.dist-info/WHEEL +4 -0
- pmt4api-1.0.0.dist-info/licenses/LICENSE.txt +29 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Changelog — wmt4api
|
|
2
|
+
|
|
3
|
+
What changed in each release, from the point of view of the code you write. Versions follow semantic
|
|
4
|
+
versioning: a patch release (3.1.x) changes no public API.
|
|
5
|
+
|
|
6
|
+
## [3.2.0] — 2026-10-12
|
|
7
|
+
Additive release. **No breaking change** — code that builds against 3.1.2 builds unchanged against 3.2.0.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`GetSymbolNames()`** returns the names of every symbol the broker offers on the account, sorted. Before, a name
|
|
11
|
+
could only be looked up if you already knew it; brokers add their own suffixes (`EURUSDm`, `XAUUSD.s`), so there was
|
|
12
|
+
no way to discover them.
|
|
13
|
+
- **`NoServerMessagesTimeout`** (ms, default 30000, 0 disables): the session is declared dead and closed when nothing
|
|
14
|
+
at all has arrived for that long. A healthy session is never silent for more than a few seconds, so this catches a
|
|
15
|
+
link that stopped carrying traffic without being closed — which the operating system reports only after many
|
|
16
|
+
minutes, if at all. The close raises `OnDisconnect` and starts `AutoReconnect`, like any other drop.
|
|
17
|
+
- `GetQuote` now fills in `Time` (the server time of the tick), `High`, `Low` and `Spread`, and returns the symbol's
|
|
18
|
+
canonical name in `Symbol` whatever case you asked with.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **A cached quote could be stale without any sign of it.** The server streams a default set of symbols until you
|
|
22
|
+
subscribe to something; from then on it streams only what you subscribed. A price cached from the default feed for
|
|
23
|
+
a symbol you had not subscribed therefore stopped updating, yet `GetQuote`, `GetTickValue` and the equity and
|
|
24
|
+
margin figures kept using it. They now subscribe the symbol and wait for a tick that arrives afterwards (if none
|
|
25
|
+
arrives in time — a closed market — the last price held is used, and `Quote.Time` says how old it is). Prices
|
|
26
|
+
from an earlier session are also discarded on a reconnect.
|
|
27
|
+
- **A position's symbol was subscribed only at connect.** A position that appeared later — one you opened, or one
|
|
28
|
+
another terminal opened on the account — was priced from whatever the cache held, and once anything was
|
|
29
|
+
subscribed the server stopped sending it, so its `Order.Profit`, `AccountEquity` and margin figures froze. The
|
|
30
|
+
symbol is now subscribed as soon as the position exists.
|
|
31
|
+
- **A server that stalled or hung up while the account data was loading** (after the login) was not failed over
|
|
32
|
+
from, and its socket stayed open until garbage collection. It is now treated like one that cannot be dialled: the
|
|
33
|
+
next endpoint is tried and the socket is closed.
|
|
34
|
+
- **`Disconnect()` did not abort a connect in flight**: with the primary silent and backups listed, the call
|
|
35
|
+
carried on dialling them for tens of seconds. It now aborts the handshake, the data load and the remaining
|
|
36
|
+
candidates. A `Reconnect()` you call yourself after a `Disconnect()` starts a session again (the supervisor
|
|
37
|
+
still never undoes your `Disconnect()`).
|
|
38
|
+
- **`IsInvestor` was never set**, so an investor (read-only) login was not recognised and the `InvestorModeException`
|
|
39
|
+
guard never fired (the server's own refusal arrived instead). It is now read from the account record, the field
|
|
40
|
+
the vendor client reads. (Checked on trading logins; no investor password was available to exercise the true case.)
|
|
41
|
+
- **A transient broker refusal of a reconnect** (a busy server, too many requests) ended `AutoReconnect` for good, as
|
|
42
|
+
if the password were wrong. Only the refusals waiting cannot cure — client too old, not enough rights, account
|
|
43
|
+
disabled, invalid account or password — stop it now; the rest are retried with the usual growing delay.
|
|
44
|
+
- `Unsubscribe` of a symbol that has an open position is ignored (the position's profit and the account's equity are
|
|
45
|
+
priced from that feed), and a symbol you subscribe explicitly waits for a tick that arrives after the request
|
|
46
|
+
instead of returning what the cache held.
|
|
47
|
+
- `CalculateLots` refused nothing for a NaN or infinite input and returned NaN lots; it now refuses. The rounding
|
|
48
|
+
down to the step no longer loses a whole step to binary dust (entry 1.1000, stop 1.0950, risk 50 sized to 0.09
|
|
49
|
+
lots instead of 0.10).
|
|
50
|
+
- `NoServerMessagesTimeout` below 10 s is treated as 10 s: a shorter limit would close a healthy, quiet session.
|
|
51
|
+
- **`Connect()` after a drop or a `Disconnect()`** did not restore the quote subscriptions (`Subscribe` of a name
|
|
52
|
+
already in the set sent nothing), so quotes did not resume. It now does, and calling `Connect()` on a connected
|
|
53
|
+
client is a no-op instead of opening a second session.
|
|
54
|
+
- **`Disconnect()` while a reconnect was in flight** could be undone: the reconnect completed, the client reported
|
|
55
|
+
`Connected` and the session ran on. `Disconnect()` now wins, and what the reconnect had opened is closed.
|
|
56
|
+
- A server that accepted the connection and then said nothing made `Connect()` wait forever; the handshake and the
|
|
57
|
+
initial data load now give up after 30 s (and the next endpoint is tried).
|
|
58
|
+
- The heartbeat was only sent when something had arrived, so a session whose feed went quiet depended on the server
|
|
59
|
+
to keep it alive. The client now pings every 4 s on its own clock.
|
|
60
|
+
- The symbol table is replaced in one step on a reconnect, so a quote tick or a call from another thread can no
|
|
61
|
+
longer see it half rebuilt.
|
|
62
|
+
|
|
63
|
+
## [3.1.2] — 2026-10-12
|
|
64
|
+
Maintenance release. **No API change** — code that builds against 3.1.1 builds unchanged against 3.1.2.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
- The licence notice, the package metadata and the support and key contact lines now name **mtapi.one** as the
|
|
68
|
+
licensor and the place to contact. The behaviour of the SDK is unchanged.
|
|
69
|
+
|
|
70
|
+
## [3.1.1] — 2026-10-11
|
|
71
|
+
Maintenance release. **No API change** — code that builds against 3.1.0 builds unchanged against 3.1.1.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
- **`ReconnectMaxAttempts` was not a cap.** After that many failed attempts the watchdog started over, so a
|
|
75
|
+
permanent failure meant a broker login and a licence request every few seconds, indefinitely. It now stops
|
|
76
|
+
after `ReconnectMaxAttempts` attempts, stops at once when the licence service or the broker *refuses* the
|
|
77
|
+
login (an expired or revoked licence, a login not allowed, an account limit, a bad password), and backs off
|
|
78
|
+
exponentially (the configured delay, doubling up to 60 s) between attempts. `Reconnect()` or `Connect()`
|
|
79
|
+
resumes self-healing afterwards.
|
|
80
|
+
- A login that the broker accepted but the licence refused no longer leaves its socket open until garbage
|
|
81
|
+
collection.
|
|
82
|
+
- `Connect()` to an unreachable host or port now throws the documented typed exception: `ConnectException`, or
|
|
83
|
+
`TimeoutException` when no endpoint answered within `ConnectTimeout`. Before, it threw a plain
|
|
84
|
+
`System.Exception`, so a mistyped host surfaced as an unhandled error in code that caught the documented types.
|
|
85
|
+
Both derive from `Exception`, so code that already caught `Exception` is unaffected.
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
- Internal clean-up of the shipped library. No public type, member or signature changed.
|
|
89
|
+
- The licence request now also carries the SDK name and version (for example `wmt4api/3.1.1`), so the licence
|
|
90
|
+
service can tell which release is calling. Nothing else is sent in addition (see `docs/SECURITY.md`).
|
|
91
|
+
- The IntelliSense documentation (`wmt4api.xml`) was reworded: no member changed; several summaries were
|
|
92
|
+
corrected where they described behaviour that no longer applies or promised more than the library does.
|
|
93
|
+
|
|
94
|
+
### Documentation and samples
|
|
95
|
+
- The guides were rewritten for the 3.x API (`MT4API`, `OrderType`, `OrderCancel`, …): the quick start, API
|
|
96
|
+
reference, errors, licensing and security pages previously described the 2.x names.
|
|
97
|
+
- `README.md` states the real relationship with the MT5 SDK — the same names, different signatures — with a
|
|
98
|
+
table of the differences.
|
|
99
|
+
- `README.md` and the guides describe `CalculateLots` as it behaves: what it reads from the account, when it
|
|
100
|
+
refuses, and that no cent-account conversion is applied.
|
|
101
|
+
- `MIGRATION.md` was corrected: `Order.Type` keeps its name in 3.x (only its type changed, `Op` →
|
|
102
|
+
`OrderType`); added the `SymbolSpec.Execution` → `ExecutionType` and `OrderDeleteAsync` →
|
|
103
|
+
`OrderCancelAsync` renames.
|
|
104
|
+
- `connect.cs`, `pnl.cs`, `sizing.cs` and `Sample.cs`: an unreachable host or port now ends with a one-line
|
|
105
|
+
message instead of an unhandled exception.
|
|
106
|
+
- `docs/ERRORS.md` lists what `Connect()` actually throws, the common MT4 trade-return codes, and the
|
|
107
|
+
`AccountLimitReached` licence reason.
|
|
108
|
+
|
|
109
|
+
### Notes
|
|
110
|
+
- `EquityPoint.RealizedPL` is still present and returns the same value as `BalanceChange`. It is obsolete and
|
|
111
|
+
may be removed in a future major release (earlier builds said 3.0.0; that did not happen). Use
|
|
112
|
+
`BalanceChange`, or `GetAccountSummary().RealizedProfit` for the trading result.
|
|
113
|
+
- `OrderSend` accepts a `magic` argument but does not transmit it in this release, so `Order.ExpertId` reads 0
|
|
114
|
+
on orders placed through this client. Use `comment` to tag orders.
|
|
115
|
+
- `AccountMargin`, `AccountFreeMargin` and `AccountMarginLevel` may wait up to `GetQuoteTimeoutMs` the first
|
|
116
|
+
time a conversion-pair quote has to be fetched; call `UpdateProfits()` after connect to warm them.
|
|
117
|
+
|
|
118
|
+
## [3.1.0] — 2026-09-11
|
|
119
|
+
Position sizing. Additive — no breaking changes.
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
- **`CalculateLots(symbol, entry, stop, risk, lotStep, minLot, maxRatio, maxEquityShare, maxLot)`** →
|
|
123
|
+
`LotSizing` — turns a risk budget and a stop price into a volume, rounded **down** to the broker's volume
|
|
124
|
+
step, and **refuses rather than guessing** when it cannot size safely (`Ok` is `false`, `Lots` is 0 and
|
|
125
|
+
`Reason` says why). `LotSizing` carries `Ok`, `Lots`, `ExpectedLossAtStop`, `RiskRequested`,
|
|
126
|
+
`MinimumTradeCost` (what the broker's smallest trade costs at your stop, so you can tell whether the account
|
|
127
|
+
is big enough for the instrument), `Ratio`, `Reason` and `Warnings`. The MT5 SDK has a `CalculateLots` and a
|
|
128
|
+
`LotSizing` of the same names.
|
|
129
|
+
- **`GetAskTickValue(Quote)` / `GetBidTickValue(Quote)`** — the direction-aware value of a price unit per lot in
|
|
130
|
+
the account currency (a SELL sizes against the bid side). The same quantity as `GetTickValue`.
|
|
131
|
+
- **`SymbolSpec.MinLot` / `MaxLot` / `LotStep` / `SecurityIndex`** — the broker's per-symbol volume rules, read
|
|
132
|
+
from the account's security group; `CalculateLots` uses them automatically (pass an argument to override a
|
|
133
|
+
specific one).
|
|
134
|
+
- **`sizing.cs`** quick start added to the package.
|
|
135
|
+
|
|
136
|
+
### Notes
|
|
137
|
+
- Sizing uses `GetTickValue`, which converts through direct, inverse and USD-bridge pairs into the account
|
|
138
|
+
currency, so there is no calibration step: sizing works as soon as the symbol has a live quote. Amounts are
|
|
139
|
+
in the currency the account reports; no cent-unit conversion is applied.
|
|
140
|
+
|
|
141
|
+
## [3.0.0] — 2026-09-11
|
|
142
|
+
A breaking release that renames the MT4 API to use the same names as the MT5 SDK (`wmt5api`). Names and a few
|
|
143
|
+
usage patterns changed; the connection behaviour and every calculation are unchanged. Existing 2.x code must be
|
|
144
|
+
updated — see **`MIGRATION.md`** for the full old → new map.
|
|
145
|
+
|
|
146
|
+
### Changed (breaking — renamed to match MT5)
|
|
147
|
+
- **Main client class `QuoteClient` → `MT4API`** (as MT5 uses `MT5API`).
|
|
148
|
+
- **Trading is available on the client:** call `OrderSend` / `OrderModify` / `OrderClose` / `OrderCancel` /
|
|
149
|
+
`OrderCloseBy` / `OrderMultipleCloseBy` (and the `*Async` forms) directly on `MT4API`. The method names match
|
|
150
|
+
the MT5 SDK; the argument lists differ (see the README). The first call creates the execution client for you;
|
|
151
|
+
the `OrderClient` property remains for the `OnOrderProgress` event and the awaitable `Order*AsyncTask` forms.
|
|
152
|
+
- **`OrderDelete` → `OrderCancel`** (cancel a pending order), with `OrderDeleteAsync` → `OrderCancelAsync` and
|
|
153
|
+
`OrderDeleteAsyncTask` → `OrderCancelAsyncTask`.
|
|
154
|
+
- **`GetQuote(symbol, timeout)` now returns a `Quote`** (`.Bid` / `.Ask`, or `null` when there is no quote), not
|
|
155
|
+
a `(bid, ask)` tuple.
|
|
156
|
+
- **Enum `Op` → `OrderType`**; `Order.Type` keeps its name and now has the type `OrderType`.
|
|
157
|
+
- **`Order.MagicNumber` → `ExpertId`**, **`Order.Expiration` → `ExpirationTime`**.
|
|
158
|
+
- **`AccountInfo.Name` → `UserName`** (the account holder name).
|
|
159
|
+
- **`ConnectTimeoutMs` → `ConnectTimeout`**, **`ServerBuild` → `ServerTradeBuild`**,
|
|
160
|
+
**`DownloadQuoteHistoryTimeoutMs` → `DownloadQuoteHistoryTimeout`**.
|
|
161
|
+
- **Type renames:** `QuoteEventArgs` → `Quote`, `OrderUpdateEventArgs` → `OrderUpdate`,
|
|
162
|
+
`OrderProgressEventArgs` → `OrderProgress`, `UpdateAction` → `UpdateType`, `Execution` → `ExecutionType`
|
|
163
|
+
(and `SymbolSpec.Execution` → `SymbolSpec.ExecutionType`), `ConSymbolGroup` → `SymGroup`.
|
|
164
|
+
|
|
165
|
+
### Added
|
|
166
|
+
- **`UpdateProfits()`** (parameterless) — warms every open-position symbol so the next account read prices from
|
|
167
|
+
live ticks. (`UpdateProfits(symbol)` still exists for a single symbol.)
|
|
168
|
+
- **`ApiKey` also reads `WMTAPI_KEY`** when `WMT4API_KEY` is not set.
|
|
169
|
+
- **`Bar.OpenPrice` / `HighPrice` / `LowPrice` / `ClosePrice`** — alternative names for `Open` / `High` / `Low` /
|
|
170
|
+
`Close` (the names the MT5 SDK uses), over the same values.
|
|
171
|
+
|
|
172
|
+
### Unchanged (MT4 platform-specific — kept by design)
|
|
173
|
+
- `int` login (MT4 logins are 32-bit), `servers.ini` access (`LoadServersIni` / `SaveServersIni`), the
|
|
174
|
+
order-centric model (`OrderType.Balance` / `Credit` rows), `OrderCloseBy` / `OrderMultipleCloseBy`,
|
|
175
|
+
`LoginRejectedException` / `RequoteException` / `TradeTimeoutException`, and `GetSymbolSpec`.
|
|
176
|
+
|
|
177
|
+
## [2.4.1] — 2026-08-30
|
|
178
|
+
|
|
179
|
+
### Fixed
|
|
180
|
+
- **`OrderSend` and its siblings now refuse a volume that is not a real volume.** A `NaN`, negative or infinite
|
|
181
|
+
volume used to be converted to a number and sent — for example an infinite volume, which is what a sizing
|
|
182
|
+
formula produces when the stop sits on the entry price. Such volumes now throw `ArgumentOutOfRangeException`
|
|
183
|
+
naming the value received, and nothing is sent. The call refuses rather than clamping, because clamping would
|
|
184
|
+
turn a programming error into a real order at a size nobody chose.
|
|
185
|
+
**This is a behaviour change:** code that sent such a volume and got a silent result now gets an exception.
|
|
186
|
+
`ArgumentOutOfRangeException` derives from `ArgumentException`, so existing `catch` blocks still catch it.
|
|
187
|
+
Valid volumes are unchanged.
|
|
188
|
+
- **`GetTickValue` and `CalculateOrderProfit` returned `NaN` for CFD and futures symbols on brokers that name
|
|
189
|
+
their FX pairs with a suffix.** The conversion-pair lookup asked for the bare six-character name (`USDTHB`)
|
|
190
|
+
where the broker lists the suffixed one (`USDTHBm`). The suffix is now discovered from the broker's own
|
|
191
|
+
symbol table.
|
|
192
|
+
|
|
193
|
+
### Documentation
|
|
194
|
+
- `NaN` from `GetTickValue` is explained correctly: the commonest cause is that a **currency conversion**
|
|
195
|
+
could not be resolved, which can happen even with a live, cached quote — not only a missing quote.
|
|
196
|
+
- `GetTickValue` is described as the raw value per price unit per lot, not as a ready-made sizing recipe.
|
|
197
|
+
|
|
198
|
+
## [2.4.0] — 2026-08-21
|
|
199
|
+
|
|
200
|
+
### Added
|
|
201
|
+
- **`GetAccountSummary([from, to])`** → `AccountSummary { Balance, Credit, RealizedProfit, Deposit, Withdrawal }`
|
|
202
|
+
— the realized account figures a terminal shows in its account-history view, computed from the order ledger.
|
|
203
|
+
`RealizedProfit` is the cumulative closed-trade P/L (commission and swap included) — the terminal's "Profit",
|
|
204
|
+
**distinct from `AccountProfit`, which is the FLOATING P/L of the open positions**. `Deposit` / `Withdrawal`
|
|
205
|
+
are the balance operations (`Op.Balance` rows, by sign). All-time by default; pass a range for a bounded
|
|
206
|
+
window.
|
|
207
|
+
|
|
208
|
+
### Deprecated
|
|
209
|
+
- **`EquityPoint.RealizedPL` → `EquityPoint.BalanceChange`.** The old name claimed a trading result; the value
|
|
210
|
+
is a **balance difference**, so it also contains deposits and withdrawals — on a funded account that had lost
|
|
211
|
+
money it read as a large profit. The new name says what the number is; for the trading result use
|
|
212
|
+
`GetAccountSummary().RealizedProfit`.
|
|
213
|
+
**This is not a breaking change.** `RealizedPL` is still the field and `BalanceChange` reads and writes the
|
|
214
|
+
same value. Existing source still compiles (with an obsolete warning), and existing compiled programs keep
|
|
215
|
+
working when the new DLL is dropped in beside them.
|
|
216
|
+
|
|
217
|
+
### Fixed
|
|
218
|
+
- **The quick starts handle connect failures instead of crashing.** `connect.cs` and `pnl.cs` tested
|
|
219
|
+
`if (!Connected)` after `Connect()` — a line that can never run, because `Connect()` throws rather than
|
|
220
|
+
returning a flag. They now catch the typed failures and print one actionable line.
|
|
221
|
+
|
|
222
|
+
### Documentation
|
|
223
|
+
- **`pnl.cs` no longer reports deposits as profit.** It now shows the realized trading result from
|
|
224
|
+
`GetAccountSummary`, lists deposits and withdrawals beside it, and prints the identity
|
|
225
|
+
`balance change = realized trading P/L + deposits + withdrawals`.
|
|
226
|
+
- `EquityPoint.RealizedPL` / `BalanceChange` summaries lead with BALANCE CHANGE and point to
|
|
227
|
+
`GetAccountSummary` for the trading result.
|
|
228
|
+
- README: every top-level `.cs` is a complete program with its own `Main()`; building several in one project
|
|
229
|
+
fails with `CS0017`.
|
|
230
|
+
|
|
231
|
+
## [2.3.2] — 2026-08-16
|
|
232
|
+
|
|
233
|
+
### Fixed
|
|
234
|
+
- `CalculateEquityHistory` historical `Equity` / `UnrealizedPL`: a position that was open at a step is now
|
|
235
|
+
priced from the bar **at or before** that step (and contributes 0 floating when no bar covers that time)
|
|
236
|
+
instead of from the most recent bar, which could price an old position from a far-later quote and produce a
|
|
237
|
+
wildly wrong, even sign-flipped, figure on deep history. `Balance` / `RealizedPL` are unaffected.
|
|
238
|
+
|
|
239
|
+
### Documentation
|
|
240
|
+
- The README states that historical `Equity` / `UnrealizedPL` are a reconstruction priced from bars
|
|
241
|
+
(approximate, bounded by the broker's bar history), while `Balance` / `RealizedPL` come from the order ledger.
|
|
242
|
+
|
|
243
|
+
## [2.3.1] — 2026-08-15
|
|
244
|
+
|
|
245
|
+
### Added
|
|
246
|
+
- **`pnl.cs`** quick start: the floating P/L of the open positions next to the realized total since a date and
|
|
247
|
+
the balance/equity curve, from `CalculateEquityHistory`.
|
|
248
|
+
- README: a section separating the floating P/L (`AccountProfit`) from the realized total, and
|
|
249
|
+
`CalculateEquityHistory(from[, tf])` in the capability table.
|
|
250
|
+
|
|
251
|
+
The library itself is unchanged from 2.3.0.
|
|
252
|
+
|
|
253
|
+
## [2.3.0] — 2026-08-14
|
|
254
|
+
|
|
255
|
+
### Added
|
|
256
|
+
- **`LoadServersIni` / `SaveServersIni`** — read and write a `servers.ini` file offline (no connection), as
|
|
257
|
+
`Mt4Server[] { Name, Company, IsDemo, MainHost, (Host, Port)[] Access }`. Use it to discover or persist the
|
|
258
|
+
brokers and their access servers from a terminal's `servers.ini`.
|
|
259
|
+
- **`connect.cs`** quick start (fill in your login → connects and prints the live account state: balance,
|
|
260
|
+
equity, floating P/L, margin, free margin, margin level — using the warm-first read pattern so the floating
|
|
261
|
+
figures aren't read cold as zero) and **`servers.cs`** (list every access server host:port each broker
|
|
262
|
+
publishes, read offline from a `servers.ini` file — no login needed).
|
|
263
|
+
|
|
264
|
+
## [2.2.1] — 2026-08-14
|
|
265
|
+
|
|
266
|
+
### Added
|
|
267
|
+
- **`AccountMarginLevel`** — margin level (percent) = equity / margin × 100, the same ratio the terminal shows;
|
|
268
|
+
0 when the account is flat (no used margin) or before connect.
|
|
269
|
+
|
|
270
|
+
### Documentation
|
|
271
|
+
- Documented the warm-first pattern for reading account state (`AccountEquity` / `AccountProfit` /
|
|
272
|
+
`AccountFreeMargin` / `AccountMarginLevel` reflect the floating P/L of open positions and are computed from
|
|
273
|
+
cached quotes — warm the open-position symbols before reading), and completed the capability table
|
|
274
|
+
(`AccountMargin` / `AccountFreeMargin` / `AccountMarginLevel`). `Sample.cs` (`ShowAccount`) warms then prints
|
|
275
|
+
the full account state.
|
|
276
|
+
|
|
277
|
+
## [2.2.0] — 2026-08-12
|
|
278
|
+
|
|
279
|
+
### Added
|
|
280
|
+
- **Account margin** — `AccountMargin`, `AccountFreeMargin`, `AccountLeverage`, `AccountType`. Margin is derived
|
|
281
|
+
on the client (the server sends only balance and credit), like the terminal: Forex via leverage, CFD via the
|
|
282
|
+
symbol's margin divider, converted to the account currency.
|
|
283
|
+
- `RequiredMargin(symbol, lots, type, price)` — pre-trade margin estimate.
|
|
284
|
+
- `Unsubscribe(symbol)` / `IsSubscribed(symbol)` — the other half of the quote-subscription lifecycle.
|
|
285
|
+
- `UpdateProfits(symbol)` / `UpdateMargin()` — force-refresh a symbol's cached quote so `AccountEquity` /
|
|
286
|
+
`AccountProfit` / `AccountMargin` reprice from a current tick.
|
|
287
|
+
- `DownloadOrderHistoryTimeoutMs` (default 30000, 0 = wait indefinitely).
|
|
288
|
+
|
|
289
|
+
### Fixed
|
|
290
|
+
- **`DownloadOrderHistory(from, to)`** now waits up to `DownloadOrderHistoryTimeoutMs` and throws
|
|
291
|
+
`TimeoutException` instead of hanging when a broker withholds history; the equity-curve builder inherits the
|
|
292
|
+
bound.
|
|
293
|
+
|
|
294
|
+
## [2.1.0] — 2026-07-24
|
|
295
|
+
|
|
296
|
+
### Added — events and async trading
|
|
297
|
+
All new; no behavioural change to existing calls.
|
|
298
|
+
|
|
299
|
+
- **Events** (standard .NET shape `(object sender, args)`): `OnQuote` (live ticks; the quote carries
|
|
300
|
+
`High` / `Low` / `Spread` from the session refresh, plus `GetBid()` / `GetAsk()`), `OnConnect`,
|
|
301
|
+
`OnDisconnect`, `OnOrderUpdate`, `OnQuoteHistory`, `OnSymbolsUpdate`, and `OnOrderProgress` on `OrderClient`.
|
|
302
|
+
Library diagnostics stream through the static `Logger.OnMsg` / `OnMsgEx`.
|
|
303
|
+
- **Trading** — `OrderSend` gained an optional trailing `PlacedType` argument (source-compatible; recompile
|
|
304
|
+
callers that were built against an earlier release); a full **async family**:
|
|
305
|
+
`Order{Send,Close,Modify,Delete,CloseBy,MultipleCloseBy}Async` return a temp id (the result arrives through the
|
|
306
|
+
events), plus awaitable `Order*AsyncTask` → `Task<Order>`.
|
|
307
|
+
- **Symbols** — `Groups` lists the broker's configured symbol groups.
|
|
308
|
+
|
|
309
|
+
## [2.0.3] — 2026-07-19
|
|
310
|
+
|
|
311
|
+
### Documentation
|
|
312
|
+
Documentation-only release: the quick start, licensing and security pages described the licence key and the
|
|
313
|
+
licence service address incorrectly, which caused connection failures. Corrected. The library and API are
|
|
314
|
+
unchanged from 2.0.2.
|
|
315
|
+
|
|
316
|
+
## [2.0.2] — 2026-07-16
|
|
317
|
+
|
|
318
|
+
### Documentation — "Staying connected" (AutoReconnect)
|
|
319
|
+
Documented the built-in resilient-connection feature (no code change): opt-in `AutoReconnect` re-establishes
|
|
320
|
+
a dropped session with **broker server-list failover and symbol re-subscribe**, retrying every
|
|
321
|
+
`ReconnectBackoffMs` (a fixed delay, not exponential) up to `ReconnectMaxAttempts` (0 = until `Disconnect()`).
|
|
322
|
+
You can also drive it yourself with the public `Reconnect()` (re-establish + re-subscribe, returns a bool).
|
|
323
|
+
`Connected` is kept current by a heartbeat (~4–8 s). MT4 has no connect-progress event. The README and Sample
|
|
324
|
+
show both paths and warn against the poll-`Connected`-and-`Connect()` connect-storm pattern.
|
|
325
|
+
|
|
326
|
+
## [2.0.1] — 2026-07-15
|
|
327
|
+
|
|
328
|
+
### Fixed — the licence service address now defaults to the hosted service
|
|
329
|
+
`TokenServiceUrl` now defaults to the hosted licence service instead of a local address, so setting **only**
|
|
330
|
+
your `ApiKey` connects out of the box. Previously, if you did not also set the service address, `Connect()`
|
|
331
|
+
failed with a `TokenServiceException` (service unreachable). The `WMT4API_TOKEN_URL` environment variable and
|
|
332
|
+
the `TokenServiceUrl` field still override the default. The Sample now sets `ApiKey` explicitly.
|
|
333
|
+
|
|
334
|
+
## [2.0.0] — 2026-06-20
|
|
335
|
+
|
|
336
|
+
### Breaking — dynamic methods now return `double.NaN` instead of `0` when unresolved
|
|
337
|
+
`GetTickValue` and `CalculateOrderProfit` now return `double.NaN` when no live quote has resolved yet
|
|
338
|
+
(previously `0`). This makes the "not yet ready" state unambiguous and distinguishable from a legitimate zero.
|
|
339
|
+
|
|
340
|
+
**Migration:** if your code tested the return value with `== 0` as an "unresolved" sentinel, switch to
|
|
341
|
+
`double.IsNaN(value)`. If you already guard with `tickValue > 0` (as `Sample.cs` shows), no change is needed —
|
|
342
|
+
`NaN > 0` is `false`.
|
|
343
|
+
|
|
344
|
+
### Added
|
|
345
|
+
- **`GetTickValue(symbol[, timeout])`** — the account-currency value of a full 1.0 price-unit move per 1.0 lot.
|
|
346
|
+
It subscribes the symbol and waits for a live quote; the timeout defaults to 5000 ms.
|
|
347
|
+
- `CalculateOrderProfit` is documented as a reporting function: it converts the P/L of a price move into the
|
|
348
|
+
account currency.
|
|
349
|
+
|
|
350
|
+
## [1.1.0] — 2026-06-17
|
|
351
|
+
|
|
352
|
+
### Added
|
|
353
|
+
- **`LoginRejectedException`** (with `Code`) — thrown at `Connect()` when the broker rejects the account number
|
|
354
|
+
or password, instead of a bare `Exception`, so it can be told apart from a licence failure or an SDK fault.
|
|
355
|
+
- **`LicenseReason.AccountLimitReached`** — a licence can carry a limit on the number of distinct broker logins;
|
|
356
|
+
connecting a new login beyond it throws `LicenseException` with this reason. Logins already enrolled keep
|
|
357
|
+
working.
|
|
358
|
+
|
|
359
|
+
### Documentation
|
|
360
|
+
- `Servers` is documented as a read-only list of `(host, port)` tuples that you mutate, not assign.
|
|
361
|
+
- `ApiKey`, `TokenServiceUrl` and `TokenServiceTimeout` are documented as public fields, not properties.
|
|
362
|
+
- The Quickstart constructor example compiles against the Sample (`user:` rather than `login:`).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
THIRD-PARTY NOTICES — wmt4api
|
|
2
|
+
==============================
|
|
3
|
+
|
|
4
|
+
wmt4api.dll references only the .NET platform (netstandard 2.0). It bundles no third-party library.
|
|
5
|
+
|
|
6
|
+
It contains an implementation of the LZO1X-compatible decompression format, which it uses to read the
|
|
7
|
+
compressed records a trading server sends.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Errors & troubleshooting — wmt4api
|
|
2
|
+
|
|
3
|
+
Errors are **typed** — catch what you care about. A licence refusal stops the connect before it completes:
|
|
4
|
+
nothing is read from, or traded on, the account. (The broker login handshake has already taken place by then;
|
|
5
|
+
what is refused is the licence, before any account data is loaded.)
|
|
6
|
+
|
|
7
|
+
The snippets use ordinary local variables you declare yourself: `api` (an `MT4API`), `symbol`, `lots`,
|
|
8
|
+
`slippage`, `from` and `to`.
|
|
9
|
+
|
|
10
|
+
```csharp
|
|
11
|
+
try
|
|
12
|
+
{
|
|
13
|
+
api.Connect();
|
|
14
|
+
Quote tick = api.GetQuote(symbol, 5000);
|
|
15
|
+
api.OrderSend(symbol, OrderType.Buy, lots, tick.Ask, slippage);
|
|
16
|
+
}
|
|
17
|
+
catch (LicenseException ex) { /* licence refused — inspect ex.Reason */ }
|
|
18
|
+
catch (TokenServiceException ex) { /* licence service unreachable or timed out — retry */ }
|
|
19
|
+
catch (LoginRejectedException ex) { /* the broker rejected the login — ex.Code; bad account number or password */ }
|
|
20
|
+
catch (RequoteException ex) { /* price moved — ex.Bid / ex.Ask are the new prices; re-quote and resend */ }
|
|
21
|
+
catch (TradeTimeoutException ex) { /* no trade reply in time — reconcile with GetOpenedOrders before resending */ }
|
|
22
|
+
catch (InvestorModeException ex) { /* a read-only (investor) login cannot trade */ }
|
|
23
|
+
catch (TimeoutException ex) { /* no endpoint answered within ConnectTimeout — check the host and port */ }
|
|
24
|
+
catch (ConnectException ex) { /* no endpoint could be reached, or the connection dropped while a trade was in flight */ }
|
|
25
|
+
catch (ServerException ex) { /* the broker rejected the trade — ex.Code is the MT4 trade-return code */ }
|
|
26
|
+
catch (ArgumentOutOfRangeException ex) { /* the volume is not a real volume — nothing was sent */ }
|
|
27
|
+
catch (Exception ex) { /* anything else */ }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`Connect()` throws the typed licence/service exception **directly** (it is not wrapped in an
|
|
31
|
+
`AggregateException`).
|
|
32
|
+
|
|
33
|
+
## LicenseException — the licence service refused
|
|
34
|
+
`ex.Reason` is a `LicenseReason`. `ex.ServerError` and `ex.HttpStatus` carry the service's raw answer for support.
|
|
35
|
+
|
|
36
|
+
| Reason | Meaning | What to do |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `NoKey` | No API key supplied | Set `ApiKey` or the `WMT4API_KEY` environment variable (`WMTAPI_KEY` is also read) |
|
|
39
|
+
| `Invalid` | Key not recognised | Check the key; use it exactly as issued |
|
|
40
|
+
| `Expired` | Licence period ended | Renew the subscription |
|
|
41
|
+
| `Revoked` | Subscription cancelled / key revoked | Re-subscribe or ask for a new key |
|
|
42
|
+
| `RateLimited` | Too many requests this minute | Back off and retry; reuse a connection instead of reconnecting |
|
|
43
|
+
| `LoginNotAllowed` | This login isn't on the key's allowed-logins list | Use an allowed login, or ask for the list to be widened |
|
|
44
|
+
| `AccountLimitReached` | The licence already has its maximum number of distinct broker logins enrolled and this is a new one | Connect with an already-enrolled login, or upgrade the plan |
|
|
45
|
+
| `Unknown` | The service refused for a reason the client did not recognise | Inspect `ex.ServerError` / `ex.HttpStatus`, then retry or contact support |
|
|
46
|
+
|
|
47
|
+
## TokenServiceException — infrastructure, not a licence decision
|
|
48
|
+
The licence service was unreachable, timed out (`TokenServiceTimeout`, default 10000 ms) or returned an unusable
|
|
49
|
+
reply. The client already retries a failed contact up to three times before throwing. **Retry later**, and check
|
|
50
|
+
your network access to the licence service — do **not** re-prompt the user for a key.
|
|
51
|
+
|
|
52
|
+
## LoginRejectedException — the broker refused the login
|
|
53
|
+
Thrown at `Connect()` when the **broker's access server** rejects the login: a wrong account number or
|
|
54
|
+
password, a disabled/expired login, or an authorization refusal. `ex.Code` is the non-zero status the server
|
|
55
|
+
returned (a wrong account number or password is typically reported as 65). This is distinct from a licence
|
|
56
|
+
failure (`LicenseException`) and from the licence service being down (`TokenServiceException`). It is **not**
|
|
57
|
+
an SDK fault — fix the broker credentials. The broker login is checked first, so with both a wrong password
|
|
58
|
+
and a bad key you see this exception first. (A credential rejection is not a dial failure, so it does **not**
|
|
59
|
+
fail over across `Servers`.)
|
|
60
|
+
|
|
61
|
+
## An unreachable host
|
|
62
|
+
When no endpoint can be dialled, `Connect()` throws a **`ConnectException`** whose message names how many
|
|
63
|
+
endpoints were tried and the last error (for example `cannot connect: all 1 endpoint(s) failed; last: Connection
|
|
64
|
+
refused`). When the last attempt timed out instead (no answer within `ConnectTimeout`) it throws a
|
|
65
|
+
**`TimeoutException`**, with the timeout as its `InnerException`. Neither is a licence or login problem: check
|
|
66
|
+
the host and port. A host that answered but rejected the login is a `LoginRejectedException`.
|
|
67
|
+
|
|
68
|
+
## Trade-time exceptions
|
|
69
|
+
- **`ServerException`** — the broker rejected the trade. `ex.Code` is the MT4 trade-return code; branch on it to
|
|
70
|
+
decide whether to retry, resize, or surface the failure. Common codes (the standard MetaTrader 4 trade error
|
|
71
|
+
numbers; your broker may use others):
|
|
72
|
+
|
|
73
|
+
| Code | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| 3 | Invalid trade parameters |
|
|
76
|
+
| 128 | Trade timeout |
|
|
77
|
+
| 129 | Invalid price |
|
|
78
|
+
| 130 | Invalid stops (stop-loss / take-profit too close or on the wrong side) |
|
|
79
|
+
| 131 | Invalid volume |
|
|
80
|
+
| 132 | Market is closed |
|
|
81
|
+
| 133 | Trade is disabled (for the symbol or the account) |
|
|
82
|
+
| 134 | Not enough money |
|
|
83
|
+
| 135 | Price changed |
|
|
84
|
+
| 136 | Off quotes |
|
|
85
|
+
| 137 | Broker is busy |
|
|
86
|
+
| 141 | Too many requests |
|
|
87
|
+
| 145 | Modification denied (too close to the market) |
|
|
88
|
+
| 146 | Trade context busy |
|
|
89
|
+
| 147 | Expiration denied by the broker |
|
|
90
|
+
| 148 | Too many open and pending orders |
|
|
91
|
+
|
|
92
|
+
- **`RequoteException`** — an instant-execution price moved before the fill. `ex.Bid` / `ex.Ask` carry the new
|
|
93
|
+
prices the server is offering: re-quote and resend.
|
|
94
|
+
- **`TradeTimeoutException`** — no reply arrived within `OrderClient.TradeTimeout` (default 30 s). The outcome is
|
|
95
|
+
unknown; reconcile against `GetOpenedOrders` before blindly resending.
|
|
96
|
+
- **`InvestorModeException`** — you connected with a read-only (investor) password; trading is rejected before
|
|
97
|
+
any request leaves the client. Use a trading password.
|
|
98
|
+
- **`ConnectException`** — the session dropped while a trade was in flight. The outcome is unknown; reconnect and
|
|
99
|
+
reconcile.
|
|
100
|
+
- **`ArgumentOutOfRangeException`** (an `ArgumentException`) — the `volume` was `NaN`, infinite or not greater
|
|
101
|
+
than zero. Nothing is sent. Check the arithmetic that produced it: a stop on the entry price, or a `NaN` tick
|
|
102
|
+
value, makes a division non-finite.
|
|
103
|
+
|
|
104
|
+
## Other exceptions
|
|
105
|
+
- **`TimeoutException`** — `DownloadOrderHistory` or `DownloadQuoteHistory` did not complete in time
|
|
106
|
+
(`DownloadOrderHistoryTimeoutMs`, `DownloadQuoteHistoryTimeout`).
|
|
107
|
+
- **`OperationCanceledException`** — the `CancellationToken` you passed to `DownloadOrderHistory` was cancelled.
|
|
108
|
+
- **`InvalidOperationException`** — a quote-history request for the same symbol and timeframe is already in
|
|
109
|
+
flight.
|
|
110
|
+
|
|
111
|
+
## Common issues
|
|
112
|
+
- **`Connect()` fails to reach the broker** → the host:port is wrong or unreachable. `ConnectTimeout` bounds each
|
|
113
|
+
attempt. Brokers often publish several access servers: the client fails over across `Servers` (and, on a
|
|
114
|
+
reconnect, the list the broker published at the earlier connect) when a host cannot be **dialled** (refused or
|
|
115
|
+
timed out). A handshake or login
|
|
116
|
+
rejection is not a dial failure and does not fail over, so bad credentials are not retried across hosts. Add
|
|
117
|
+
known alternates with `api.Servers.Add(("alt.broker.example", 443));` — `Servers` is a **read-only list of
|
|
118
|
+
`(string host, int port)` tuples you mutate**.
|
|
119
|
+
- **`RateLimited`** → a key has a per-minute allowance of licence requests. Every `Connect()`, every
|
|
120
|
+
`Reconnect()` attempt and every `DownloadOrderHistory` call (which opens its own short-lived broker
|
|
121
|
+
connection) is one request. Once the allowance is used further requests are refused until the minute rolls
|
|
122
|
+
over. Reuse one connection; do not poll-and-reconnect in a tight loop.
|
|
123
|
+
- **History is slow or times out** → `DownloadOrderHistory(from, to)` waits up to
|
|
124
|
+
`DownloadOrderHistoryTimeoutMs` (default 30 s) and then throws `TimeoutException`. Use the
|
|
125
|
+
`DownloadOrderHistory(from, to, timeoutMs, ct)` overload for a per-call timeout and cancellation, and narrow
|
|
126
|
+
the date range.
|
|
127
|
+
- **A quote never arrives** → `GetQuote(symbol, timeoutMs)` subscribes and waits; it returns `null` if the
|
|
128
|
+
symbol is unknown or no tick arrived in time. Symbol names are matched case-insensitively but must include
|
|
129
|
+
your broker's suffix (for example `EURUSDm`). Outside market hours there may be no tick.
|
|
130
|
+
- **`GetTickValue` or `CalculateOrderProfit` returns `NaN`** → the value could not be resolved — usually a
|
|
131
|
+
currency conversion, not a missing quote. Test with `double.IsNaN()`; `CalculateLots` refuses in this case
|
|
132
|
+
and says so in `Reason`.
|
|
133
|
+
- **`IsInvestor == true`** → you connected with a read-only (investor) password; trading is not permitted (a
|
|
134
|
+
trade throws `InvestorModeException`).
|
|
135
|
+
- **Using the client before `Connect()` succeeds** is not supported — trading, history and quote calls need a
|
|
136
|
+
live session.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Licensing & key management — wmt4api
|
|
2
|
+
|
|
3
|
+
## How authorization works
|
|
4
|
+
Every connection is authorised by our hosted **licence service**, gated by your **API key**. That happens on each
|
|
5
|
+
connect: the initial `Connect()`, every `Reconnect()` attempt (including the automatic ones), and the short-lived
|
|
6
|
+
broker connection that each `DownloadOrderHistory` call opens. Your DLL talks to your broker for trading data and
|
|
7
|
+
to the licence service only for this authorisation. No key → no connection. What is sent is listed in
|
|
8
|
+
[Security](SECURITY.md).
|
|
9
|
+
|
|
10
|
+
## Supplying the key
|
|
11
|
+
You supply **only your API key** — as a **field** on the `MT4API` or as an **environment variable**. The field
|
|
12
|
+
is initialised from the environment variable when the client is created, and assigning it overrides the
|
|
13
|
+
variable. `ApiKey`, `TokenServiceUrl` and `TokenServiceTimeout` are plain public fields — assign them directly.
|
|
14
|
+
|
|
15
|
+
| What | Field | Environment variable |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| API key | `api.ApiKey = "…"` | `WMT4API_KEY` (`WMTAPI_KEY` is read when it is not set) |
|
|
18
|
+
| Licence service address | `api.TokenServiceUrl` | `WMT4API_TOKEN_URL` |
|
|
19
|
+
| Licence request timeout | `api.TokenServiceTimeout` (ms, default 10000) | — |
|
|
20
|
+
|
|
21
|
+
```csharp
|
|
22
|
+
api = new MT4API(user, password, host, port);
|
|
23
|
+
api.ApiKey = Environment.GetEnvironmentVariable("WMT4API_KEY") ?? apiKey; // or assign the field directly
|
|
24
|
+
api.Connect(); // the service address defaults to the hosted service
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`TokenServiceUrl` already defaults to our hosted licence service — **leave it unchanged** unless support asks
|
|
28
|
+
you to change it. Keep your key out of source control and out of shipped binaries (see [Security](SECURITY.md)).
|
|
29
|
+
|
|
30
|
+
## Validity, expiry, renewal
|
|
31
|
+
- A licence is valid for your **subscription period**. When the period ends, or the subscription is cancelled,
|
|
32
|
+
the key stops authorising new connections and `Connect()` throws `LicenseException` with `Reason = Expired` or
|
|
33
|
+
`Revoked`. Once the licence is renewed, the next connect succeeds.
|
|
34
|
+
- Connections that are already open are unaffected until they drop; a reconnect needs a valid licence.
|
|
35
|
+
|
|
36
|
+
## Revoking and rotating
|
|
37
|
+
- Cancelling a subscription revokes its key: the next connect is refused with `Reason = Revoked`.
|
|
38
|
+
- To rotate a key, ask for a new one, update your deployment, and have the old one revoked.
|
|
39
|
+
|
|
40
|
+
## Rate limits, allowed logins and account limits
|
|
41
|
+
- Each key has a per-minute allowance of licence requests; every connect, reconnect attempt and history
|
|
42
|
+
download is one request. Exceeding it → `Reason = RateLimited`; back off and retry.
|
|
43
|
+
- A key may be restricted to specific broker logins. Connecting with a login outside that list →
|
|
44
|
+
`Reason = LoginNotAllowed`.
|
|
45
|
+
- A licence may carry a limit on the number of **distinct broker logins** it can be used with. When that limit is
|
|
46
|
+
reached, a new login is refused with `Reason = AccountLimitReached`; logins already enrolled keep working.
|
|
47
|
+
|
|
48
|
+
## Network access
|
|
49
|
+
From the machine that runs your program you need:
|
|
50
|
+
|
|
51
|
+
- outbound **TCP** to your broker's MT4 access servers (the host and port you pass to `MT4API`, plus any
|
|
52
|
+
`Servers` you add and the access servers the broker publishes at connect); and
|
|
53
|
+
- outbound **HTTPS (port 443)** to the licence service (`https://auth4.mtapi.one` by default).
|
|
54
|
+
|
|
55
|
+
See [Errors & troubleshooting](ERRORS.md) for the full failure matrix.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Security — wmt4api
|
|
2
|
+
|
|
3
|
+
## Your API key is a secret
|
|
4
|
+
Treat your API key like a password. Anyone with it can open licensed connections billed to your account.
|
|
5
|
+
|
|
6
|
+
- **Never commit it** to source control, log it, or embed it in client-side or shipped binaries.
|
|
7
|
+
- Prefer the **`WMT4API_KEY` environment variable** (or a secret manager) over a hard-coded `ApiKey`.
|
|
8
|
+
- The SDK does not write your key to its log output or to disk. It is sent only to the licence service, over
|
|
9
|
+
HTTPS, in the `X-Api-Key` request header.
|
|
10
|
+
- Rotate the key if you suspect exposure (ask for a new one, update your deployment, have the old one revoked).
|
|
11
|
+
|
|
12
|
+
## Broker credentials
|
|
13
|
+
- Use an **investor (read-only) password** for read-only integrations — it cannot place trades: any trading
|
|
14
|
+
call throws `InvestorModeException` before a request is sent.
|
|
15
|
+
- Your broker login and password are used only to log in to your broker's access server, as a terminal does.
|
|
16
|
+
The password is never sent to the licence service.
|
|
17
|
+
|
|
18
|
+
## Transport
|
|
19
|
+
- The licence request uses **HTTPS** to our hosted licence service. `TokenServiceUrl` defaults to it; leave it
|
|
20
|
+
unchanged, and never point it at a host you do not trust — it receives your API key.
|
|
21
|
+
- The connection to your broker is the MT4 access-server protocol, exactly as a terminal makes it.
|
|
22
|
+
|
|
23
|
+
## What leaves your machine
|
|
24
|
+
| To | What | When |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Licence service | Your API key (`X-Api-Key` header); the SDK name and version (`X-Wmt-Sdk` header); your broker login **number**; the broker server's build number; the connection-handshake data from your broker's server | each connect, reconnect attempt and history download |
|
|
27
|
+
| Your broker | Login and password (as the standard MT4 login), and your own requests: quotes, history, trades | during the session |
|
|
28
|
+
|
|
29
|
+
The licence service receives **no** broker password and **no** trading data — no orders, positions, balances,
|
|
30
|
+
quotes or history. Nothing else in the SDK sends data anywhere: no telemetry, no crash reports.
|
|
31
|
+
|
|
32
|
+
## Data handling
|
|
33
|
+
- `Account` exposes your own account details (holder name, broker company, group, balance and credit,
|
|
34
|
+
currency). This is **your** data from **your** broker; handle it per your privacy obligations if you store it.
|
|
35
|
+
- `LoadServersIni` reads only the file you name; `SaveServersIni` returns bytes and writes nothing.
|
|
36
|
+
|
|
37
|
+
## Reporting
|
|
38
|
+
Security concerns: contact your account representative at mtapi.one.
|
|
Binary file
|