pmt5api 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.
@@ -0,0 +1,131 @@
1
+ # From C# to Python
2
+
3
+ Every public member of the C# `MT5API` class and where it went. Generated from `pmt5api.COVERAGE`, which an integration test checks against the DLL: a new C# member cannot ship without an entry here.
4
+
5
+ | C# member | Python |
6
+ |---|---|
7
+ | `Account` | `Client.account` |
8
+ | `AccountCommission` | `Client.account` |
9
+ | `AccountCompanyName` | `Client.company` |
10
+ | `AccountCurrency` | `Client.account` |
11
+ | `AccountEquity` | `Client.account` |
12
+ | `AccountFee` | `Client.account` |
13
+ | `AccountFloatingProfit` | `Client.account` |
14
+ | `AccountFreeMargin` | `Client.account` |
15
+ | `AccountMargin` | `Client.account` |
16
+ | `AccountMarginLevel` | `Client.account` |
17
+ | `AccountMethod` | `Client.account_method` |
18
+ | `AccountProfit` | `Client.account` |
19
+ | `AccountSwap` | `Client.account` |
20
+ | `ApiKey` | `Client(api_key)` |
21
+ | `AuthTagsSeen` | not applicable: protocol diagnostics for the ops sentinel, of no use to an application |
22
+ | `AuthUnknownTags` | not applicable: protocol diagnostics for the ops sentinel, of no use to an application |
23
+ | `AutoReconnect` | `Client(auto_reconnect)` |
24
+ | `CalculateEquityHistory` | `Client.equity_history` |
25
+ | `CalculateLots` | `Client.lots_for_risk` |
26
+ | `CalculateOrderProfit` | `Client.order_profit` |
27
+ | `CalibrateMoneyScale` | `Client.calibrate_money_scale` |
28
+ | `ChangePassword` | `Client.change_password` |
29
+ | `ClosedOrders` | `Client.closed_orders` |
30
+ | `ClusterMemberConnectTimeout` | `Client(cluster_member_timeout)` |
31
+ | `CommonKey` | `Client(common_key)` |
32
+ | `ComputeTickValueFromDeal` | `pmt5api.tick_value_from_deal` |
33
+ | `Connect` | `Client.connect` |
34
+ | `Connected` | `Client.connected` |
35
+ | `ConnectTime` | `Client.connect_time` |
36
+ | `ConnectTimeout` | `Client(connect_timeout)` |
37
+ | `ConvertToTimeframe` | `pmt5api.aggregate` |
38
+ | `Disconnect` | `Client.disconnect` |
39
+ | `DownloadOrderHistory` | `Client.history` |
40
+ | `DownloadOrderHistoryTimeoutMs` | `Client(history_timeout)` |
41
+ | `DownloadQuoteHistoryMonth` | `Client.bars` |
42
+ | `DownloadQuoteHistoryTimeout` | `Client(bars_timeout)` |
43
+ | `DownloadQuoteHistoryToday` | `Client.bars` |
44
+ | `EnableAllOrderUpdates` | `Client(all_order_updates)` |
45
+ | `ExecutionTimeout` | `Client(execution_timeout)` |
46
+ | `GetAccountSummary` | `Client.summary` |
47
+ | `GetAskTickValue` | `Client.tick_value` |
48
+ | `GetBidTickValue` | `Client.tick_value` |
49
+ | `GetContractSize` | `Client.contract_size` |
50
+ | `GetOpenedOrders` | `Client.orders` |
51
+ | `GetQuote` | `Client.quote` |
52
+ | `GetQuoteTask` | `Client.aquote` |
53
+ | `GetQuoteTimeoutMs` | `Client(quote_timeout)` |
54
+ | `GetRequestId` | `Client.new_request_id` |
55
+ | `GetTickValue` | `Client.tick_value` |
56
+ | `Host` | `Client.host` |
57
+ | `Id` | not applicable: a free-form tag for the caller's own use; keep it in your own code |
58
+ | `IsInvestor` | `Client.is_investor` |
59
+ | `IsNotConfirmedAccount` | `Client.is_confirmed` |
60
+ | `IsQuoteSession` | `Client.is_quote_session` |
61
+ | `IsSubscribed` | `Client.is_subscribed` |
62
+ | `IsTradeAllowed` | `Client.can_trade` |
63
+ | `IsTradeDisableOnServer` | `Client.trading_disabled_by_server` |
64
+ | `IsTradeSession` | `Client.is_trade_session` |
65
+ | `LastQuoteTime` | `Client.last_quote_time` |
66
+ | `LastServerMessageTime` | `Client.last_server_message_time` |
67
+ | `LedgerValuePerUnitLot` | `Client.ledger_value_per_lot` |
68
+ | `LoadServersDat` | `pmt5api.load_servers_dat` |
69
+ | `MoneyScale` | `Client.money_scale` |
70
+ | `MoneyScaleEvidence` | `Client.money_scale_evidence` |
71
+ | `MoneyScaleFromDeal` | `Client.money_scale_from_deal` |
72
+ | `NoServerMessagesTimeout` | `Client(no_message_timeout)` |
73
+ | `OnConnectProgress` | `Client.on_connect_progress` |
74
+ | `OnOrderHistory` | `Client.on_history` |
75
+ | `OnOrderProgress` | `Client.on_order_progress` |
76
+ | `OnOrderUpdate` | `Client.on_order_update` |
77
+ | `OnQuote` | `Client.on_quote` |
78
+ | `OnQuoteHistory` | `Client.on_quote_history` |
79
+ | `OnSymbolsUpdate` | `Client.on_symbols_update` |
80
+ | `OnSymbolUpdate` | `Client.on_symbol_update` |
81
+ | `OrderCancel` | `Client.cancel` |
82
+ | `OrderCancelAsync` | `Client.cancel_async` |
83
+ | `OrderCancelAsyncTask` | `Client.acancel` |
84
+ | `OrderClose` | `Client.close` |
85
+ | `OrderCloseAsync` | `Client.close_async` |
86
+ | `OrderCloseAsyncTask` | `Client.aclose` |
87
+ | `OrderModify` | `Client.modify` |
88
+ | `OrderModifyAsync` | `Client.modify_async` |
89
+ | `OrderModifyAsyncTask` | `Client.amodify` |
90
+ | `OrderSend` | `Client.buy` |
91
+ | `OrderSendAsync` | `Client.buy_async` |
92
+ | `OrderSendAsyncTask` | `Client.abuy` |
93
+ | `Password` | `Client(password)` |
94
+ | `PfxFile` | `Client(certificate)` |
95
+ | `PfxFilePassword` | `Client(certificate_password)` |
96
+ | `PingTime` | `Client(ping_interval)` |
97
+ | `PlacedType` | `Client(placed_type)` |
98
+ | `Port` | `Client.port` |
99
+ | `ProcessEventTimeoutMs` | not applicable: the core accepts the value but never applies it (events run on the thread pool); pmt5api delivers events through one queued dispatcher instead — see Client.events_dropped |
100
+ | `ProcessServerMessagesInThread` | not applicable: documented by the core as reserved and unused |
101
+ | `ProxyEnable` | `Client(proxy)` |
102
+ | `ProxyHost` | `Client(proxy)` |
103
+ | `ProxyPassword` | `Client(proxy)` |
104
+ | `ProxyPort` | `Client(proxy)` |
105
+ | `ProxyType` | `Client(proxy)` |
106
+ | `ProxyUser` | `Client(proxy)` |
107
+ | `ReconnectBackoffMs` | `Client(reconnect_backoff)` |
108
+ | `ReconnectMaxAttempts` | `Client(reconnect_max_attempts)` |
109
+ | `ReconnectMaxBackoffMs` | `Client(reconnect_max_backoff)` |
110
+ | `RequestDealHistory` | `Client.deal_history` |
111
+ | `RequestDemoAccount` | `pmt5api.request_demo_account` |
112
+ | `RequestOrderHistory` | `Client.request_history` |
113
+ | `RequestQuoteHistoryMonth` | `Client.request_bars` |
114
+ | `RequestQuoteHistoryToday` | `Client.request_bars` |
115
+ | `RequiredMargin` | `Client.required_margin` |
116
+ | `SaveServersDat` | `pmt5api.save_servers_dat` |
117
+ | `Server` | `Client.host` |
118
+ | `ServerSymBuild` | `Client.symbol_build` |
119
+ | `ServerTime` | `Client.server_time` |
120
+ | `ServerTimeZoneInMinutes` | `Client.server_utc_offset` |
121
+ | `ServerTradeBuild` | `Client.trade_build` |
122
+ | `Subscribe` | `Client.subscribe` |
123
+ | `Subscriptions` | `Client.subscriptions` |
124
+ | `Symbols` | `Client.symbols` |
125
+ | `TokenServiceTimeout` | `Client(token_timeout)` |
126
+ | `TokenServiceUrl` | `Client(token_url)` |
127
+ | `Unsubscribe` | `Client.unsubscribe` |
128
+ | `UpdateProfits` | `Client.update_profits` |
129
+ | `User` | `Client.login` |
130
+
131
+ Enumerations are exposed as their member names (strings): `order.order_type == "Buy"`.
pmt5api/errors.py ADDED
@@ -0,0 +1,150 @@
1
+ """Exceptions raised by pmt5api.
2
+
3
+ Every error the licensed core raises is translated into one of these, so callers catch a small, stable hierarchy and
4
+ never depend on .NET exception types. The original exception is kept as ``__cause__``.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional
9
+
10
+
11
+ class WmtError(Exception):
12
+ """Base class of every pmt5api error."""
13
+
14
+
15
+ class CoreNotFoundError(WmtError):
16
+ """The licensed core library (wmt5api.dll) could not be located."""
17
+
18
+
19
+ class RuntimeNotFoundError(WmtError):
20
+ """The .NET runtime needed to host the core could not be started."""
21
+
22
+
23
+ class LicenseError(WmtError):
24
+ """The licence service refused the request.
25
+
26
+ ``reason`` is one of: NoKey, Invalid, Expired, Revoked, RateLimited, LoginNotAllowed, AccountLimitReached, Unknown.
27
+ """
28
+
29
+ def __init__(self, message: str, reason: str = "Unknown"):
30
+ super().__init__(message)
31
+ self.reason = reason
32
+
33
+
34
+ class TokenServiceError(WmtError):
35
+ """The licence service could not be reached or answered with something unusable (not a licence decision)."""
36
+
37
+
38
+ class ConnectError(WmtError):
39
+ """The broker's access server could not be reached."""
40
+
41
+
42
+ class NotConnectedError(WmtError):
43
+ """The call needs a connection and the client is not connected."""
44
+
45
+
46
+ class ConnectionLostError(WmtError):
47
+ """An established connection was lost."""
48
+
49
+
50
+ class ServerError(WmtError):
51
+ """The broker's server refused the request (for example an incorrect login or password, or a rejected order).
52
+
53
+ ``code`` is the server's return code by name (``"REQUOTE"``, ``"NO_MONEY"``, ``"INVALID_PRICE"``, ...) and
54
+ ``code_value`` its number; both are ``None`` when the refusal carried no code.
55
+ """
56
+
57
+ def __init__(self, message: str, code: Optional[str] = None, code_value: Optional[int] = None):
58
+ super().__init__(message)
59
+ self.code = code
60
+ self.code_value = code_value
61
+
62
+
63
+ class InvestorModeError(ServerError):
64
+ """The account was opened with an investor (read-only) password and the request needs trading rights."""
65
+
66
+
67
+ class ProxyError(WmtError):
68
+ """The configured proxy refused or failed."""
69
+
70
+
71
+ class RequestTimeout(WmtError, TimeoutError):
72
+ """The server did not answer within the requested time.
73
+
74
+ For a trade call this means the request WAS sent and was not confirmed in time: the order may have been accepted;
75
+ look at ``orders()`` before sending again. (When nothing was sent, :class:`NoQuoteError` is raised instead.)
76
+ """
77
+
78
+
79
+ class NoQuoteError(WmtError, TimeoutError):
80
+ """No quote was available in time to read or to trade against. Nothing was sent."""
81
+
82
+
83
+ class BusyError(WmtError):
84
+ """The core allows one request of this kind at a time and one is still running; wait for it and retry."""
85
+
86
+
87
+ class InvalidRequestError(WmtError, ValueError):
88
+ """The request was rejected before it was sent (for example, a symbol the server does not know)."""
89
+
90
+
91
+ _BY_NAME = {
92
+ "LicenseException": LicenseError,
93
+ "TokenServiceException": TokenServiceError,
94
+ "ConnectException": ConnectError,
95
+ "ConnectionException": ConnectError, # proxy handshake or client-certificate failure while connecting
96
+ "InvestorModeException": InvestorModeError,
97
+ "ServerException": ServerError,
98
+ "ProxyException": ProxyError,
99
+ "TimeoutException": RequestTimeout,
100
+ "ArgumentException": InvalidRequestError,
101
+ "SocketException": ConnectError,
102
+ }
103
+
104
+ # The core raises a few conditions as a bare System.Exception whose message is the only thing that identifies them.
105
+ _BARE_MESSAGES = (
106
+ ("Not connected", NotConnectedError),
107
+ ("Previous request still running", BusyError),
108
+ ("Download Order History Running", BusyError),
109
+ ("Cannot DownloadQuoteHistory", RequestTimeout),
110
+ ("Request Lots = 0", InvalidRequestError),
111
+ )
112
+
113
+ # Wrappers the runtime puts around the exception that matters (a failed task, a reflected call).
114
+ _WRAPPERS = ("AggregateException", "TargetInvocationException")
115
+
116
+
117
+ def translate(exc: BaseException) -> Optional[WmtError]:
118
+ """Return the pmt5api error that corresponds to a core exception, or ``None`` if it is not one of ours.
119
+
120
+ Matching is by class name along the exception's inheritance chain (most derived first), so it works without importing
121
+ the core's types and keeps working if a core exception gains a new base class.
122
+ """
123
+ if type(exc).__name__ in _WRAPPERS:
124
+ inner = getattr(exc, "InnerException", None)
125
+ if inner is not None:
126
+ return translate(inner)
127
+ for klass in type(exc).__mro__:
128
+ cls = _BY_NAME.get(klass.__name__)
129
+ if cls is None:
130
+ continue
131
+ message = str(getattr(exc, "Message", None) or exc)
132
+ if cls is LicenseError:
133
+ reason = getattr(exc, "Reason", None)
134
+ return LicenseError(message, str(reason) if reason is not None else "Unknown")
135
+ if issubclass(cls, ServerError):
136
+ code = getattr(exc, "Code", None)
137
+ try:
138
+ value = int(code) if code is not None else None
139
+ except (TypeError, ValueError):
140
+ value = None
141
+ return cls(message, str(code) if code is not None else None, value)
142
+ if cls is ConnectError and message.startswith("Disconnected"):
143
+ return ConnectionLostError(message)
144
+ return cls(message)
145
+ if type(exc).__name__ == "Exception" and getattr(type(exc), "__module__", "") == "System":
146
+ text = str(getattr(exc, "Message", None) or exc)
147
+ for prefix, cls in _BARE_MESSAGES:
148
+ if text.startswith(prefix):
149
+ return cls(text)
150
+ return None
pmt5api/frames.py ADDED
@@ -0,0 +1,21 @@
1
+ """Optional pandas helpers (``pip install pandas``)."""
2
+ from __future__ import annotations
3
+
4
+ import dataclasses
5
+ from typing import Any, Iterable
6
+
7
+
8
+ def to_frame(items: Iterable[Any]):
9
+ """A ``pandas.DataFrame`` from a list of the package's value objects (``Bar``, ``Order``, ``Quote``, ...).
10
+
11
+ A list of bars is indexed by its ``time``; anything else keeps a plain integer index.
12
+ """
13
+ try:
14
+ import pandas as pd
15
+ except ImportError as exc: # pragma: no cover - depends on the environment
16
+ raise ImportError("to_frame() needs pandas: pip install pandas") from exc
17
+ rows = [dataclasses.asdict(x) for x in items]
18
+ frame = pd.DataFrame(rows)
19
+ if "time" in frame.columns and {"open", "high", "low", "close"} <= set(frame.columns):
20
+ frame = frame.set_index("time")
21
+ return frame