fxsocket 0.1__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.
- fxsocket/__init__.py +134 -0
- fxsocket/_http.py +68 -0
- fxsocket/_version.py +1 -0
- fxsocket/client.py +254 -0
- fxsocket/config.py +12 -0
- fxsocket/enums.py +122 -0
- fxsocket/errors.py +163 -0
- fxsocket/management.py +118 -0
- fxsocket/models.py +391 -0
- fxsocket/py.typed +0 -0
- fxsocket/terminal/__init__.py +45 -0
- fxsocket/terminal/client.py +757 -0
- fxsocket/terminal/stream.py +594 -0
- fxsocket-0.1.dist-info/METADATA +256 -0
- fxsocket-0.1.dist-info/RECORD +17 -0
- fxsocket-0.1.dist-info/WHEEL +4 -0
- fxsocket-0.1.dist-info/licenses/LICENSE +21 -0
fxsocket/__init__.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""FxSocket Python SDK.
|
|
2
|
+
|
|
3
|
+
Account management today (the v1 API); per-account terminal REST and
|
|
4
|
+
WebSocket streaming for MT4/MT5 land in the next milestones.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from . import errors
|
|
10
|
+
from ._version import __version__
|
|
11
|
+
from .client import AsyncClient, Client
|
|
12
|
+
from .enums import (
|
|
13
|
+
DealEntry,
|
|
14
|
+
HealthStatus,
|
|
15
|
+
OrderKind,
|
|
16
|
+
OrderOperation,
|
|
17
|
+
Platform,
|
|
18
|
+
Timeframe,
|
|
19
|
+
TradingStatus,
|
|
20
|
+
)
|
|
21
|
+
from .errors import (
|
|
22
|
+
AccountCapError,
|
|
23
|
+
AuthError,
|
|
24
|
+
ConnectFailedError,
|
|
25
|
+
DuplicateAccountError,
|
|
26
|
+
FxSocketError,
|
|
27
|
+
NoSubscriptionError,
|
|
28
|
+
NotFoundError,
|
|
29
|
+
RateLimitError,
|
|
30
|
+
StreamError,
|
|
31
|
+
TerminalNotReadyError,
|
|
32
|
+
TerminalTimeoutError,
|
|
33
|
+
UnsupportedOnPlatformError,
|
|
34
|
+
ValidationError,
|
|
35
|
+
)
|
|
36
|
+
from .models import (
|
|
37
|
+
Account,
|
|
38
|
+
AccountInfo,
|
|
39
|
+
AccountSummary,
|
|
40
|
+
Candle,
|
|
41
|
+
Health,
|
|
42
|
+
HealthChecks,
|
|
43
|
+
HistoryTrade,
|
|
44
|
+
MarginCalc,
|
|
45
|
+
OpenedOrder,
|
|
46
|
+
OrderResult,
|
|
47
|
+
PositionTrade,
|
|
48
|
+
ProfitCalc,
|
|
49
|
+
Quote,
|
|
50
|
+
ServerTimezone,
|
|
51
|
+
SymbolInfo,
|
|
52
|
+
TerminalStatusData,
|
|
53
|
+
TradeEventData,
|
|
54
|
+
)
|
|
55
|
+
from .terminal import (
|
|
56
|
+
AccountUpdate,
|
|
57
|
+
AsyncStream,
|
|
58
|
+
AsyncTerminalClient,
|
|
59
|
+
Bar,
|
|
60
|
+
PositionsUpdate,
|
|
61
|
+
Stream,
|
|
62
|
+
StreamErrorEvent,
|
|
63
|
+
StreamEvent,
|
|
64
|
+
StreamWarning,
|
|
65
|
+
Subscribed,
|
|
66
|
+
Subscriptions,
|
|
67
|
+
TerminalClient,
|
|
68
|
+
TerminalUpdate,
|
|
69
|
+
Tick,
|
|
70
|
+
TradeUpdate,
|
|
71
|
+
UnknownEvent,
|
|
72
|
+
Unsubscribed,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"__version__",
|
|
77
|
+
"Client",
|
|
78
|
+
"AsyncClient",
|
|
79
|
+
"TerminalClient",
|
|
80
|
+
"AsyncTerminalClient",
|
|
81
|
+
"AsyncStream",
|
|
82
|
+
"Stream",
|
|
83
|
+
"StreamEvent",
|
|
84
|
+
"Tick",
|
|
85
|
+
"Bar",
|
|
86
|
+
"AccountUpdate",
|
|
87
|
+
"PositionsUpdate",
|
|
88
|
+
"TradeUpdate",
|
|
89
|
+
"TerminalUpdate",
|
|
90
|
+
"StreamWarning",
|
|
91
|
+
"Subscribed",
|
|
92
|
+
"Unsubscribed",
|
|
93
|
+
"StreamErrorEvent",
|
|
94
|
+
"Subscriptions",
|
|
95
|
+
"UnknownEvent",
|
|
96
|
+
"TradeEventData",
|
|
97
|
+
"TerminalStatusData",
|
|
98
|
+
"Account",
|
|
99
|
+
"AccountSummary",
|
|
100
|
+
"AccountInfo",
|
|
101
|
+
"OpenedOrder",
|
|
102
|
+
"HistoryTrade",
|
|
103
|
+
"PositionTrade",
|
|
104
|
+
"ServerTimezone",
|
|
105
|
+
"Quote",
|
|
106
|
+
"SymbolInfo",
|
|
107
|
+
"Candle",
|
|
108
|
+
"OrderResult",
|
|
109
|
+
"MarginCalc",
|
|
110
|
+
"ProfitCalc",
|
|
111
|
+
"Health",
|
|
112
|
+
"HealthChecks",
|
|
113
|
+
"Platform",
|
|
114
|
+
"TradingStatus",
|
|
115
|
+
"OrderOperation",
|
|
116
|
+
"OrderKind",
|
|
117
|
+
"DealEntry",
|
|
118
|
+
"HealthStatus",
|
|
119
|
+
"Timeframe",
|
|
120
|
+
"errors",
|
|
121
|
+
"FxSocketError",
|
|
122
|
+
"AuthError",
|
|
123
|
+
"RateLimitError",
|
|
124
|
+
"ValidationError",
|
|
125
|
+
"NotFoundError",
|
|
126
|
+
"AccountCapError",
|
|
127
|
+
"NoSubscriptionError",
|
|
128
|
+
"DuplicateAccountError",
|
|
129
|
+
"ConnectFailedError",
|
|
130
|
+
"TerminalNotReadyError",
|
|
131
|
+
"TerminalTimeoutError",
|
|
132
|
+
"UnsupportedOnPlatformError",
|
|
133
|
+
"StreamError",
|
|
134
|
+
]
|
fxsocket/_http.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Thin transport helpers shared by the sync and async clients.
|
|
2
|
+
|
|
3
|
+
The response handling is identical for both, so it lives in one place; the
|
|
4
|
+
sync/async split is only in who awaits the network call.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from ._version import __version__
|
|
14
|
+
from .errors import error_from_response
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def auth_headers(api_key: str) -> dict[str, str]:
|
|
18
|
+
"""Default headers carrying the API key for every request."""
|
|
19
|
+
return {
|
|
20
|
+
"X-API-Key": api_key,
|
|
21
|
+
"User-Agent": f"fxsocket-python/{__version__}",
|
|
22
|
+
"Accept": "application/json",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def process_response(resp: httpx.Response) -> Any:
|
|
27
|
+
"""Return the decoded JSON body, or raise a typed error on failure."""
|
|
28
|
+
if resp.is_success:
|
|
29
|
+
if resp.status_code == 204 or not resp.content:
|
|
30
|
+
return None
|
|
31
|
+
return resp.json()
|
|
32
|
+
raise error_from_response(resp)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SyncHTTP:
|
|
36
|
+
"""Synchronous request wrapper over an :class:`httpx.Client`."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, client: httpx.Client) -> None:
|
|
39
|
+
self._client = client
|
|
40
|
+
|
|
41
|
+
def request(
|
|
42
|
+
self,
|
|
43
|
+
method: str,
|
|
44
|
+
path: str,
|
|
45
|
+
*,
|
|
46
|
+
params: dict[str, Any] | None = None,
|
|
47
|
+
json: Any | None = None,
|
|
48
|
+
) -> Any:
|
|
49
|
+
resp = self._client.request(method, path, params=params, json=json)
|
|
50
|
+
return process_response(resp)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AsyncHTTP:
|
|
54
|
+
"""Asynchronous request wrapper over an :class:`httpx.AsyncClient`."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
57
|
+
self._client = client
|
|
58
|
+
|
|
59
|
+
async def request(
|
|
60
|
+
self,
|
|
61
|
+
method: str,
|
|
62
|
+
path: str,
|
|
63
|
+
*,
|
|
64
|
+
params: dict[str, Any] | None = None,
|
|
65
|
+
json: Any | None = None,
|
|
66
|
+
) -> Any:
|
|
67
|
+
resp = await self._client.request(method, path, params=params, json=json)
|
|
68
|
+
return process_response(resp)
|
fxsocket/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1"
|
fxsocket/client.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Top-level entry points: :class:`Client` (sync) and :class:`AsyncClient`.
|
|
2
|
+
|
|
3
|
+
Both expose ``.accounts`` (the management API), ``.terminal(account)`` (the
|
|
4
|
+
per-account terminal REST client), and ``.stream(account)`` (WebSocket
|
|
5
|
+
streaming — a sync :class:`~fxsocket.Stream` or an async
|
|
6
|
+
:class:`~fxsocket.AsyncStream`).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from ._http import AsyncHTTP, SyncHTTP, auth_headers
|
|
16
|
+
from .config import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, ENV_API_KEY
|
|
17
|
+
from .errors import AuthError, TerminalNotReadyError
|
|
18
|
+
from .management import Accounts, AsyncAccounts
|
|
19
|
+
from .models import Account
|
|
20
|
+
from .terminal.client import AsyncTerminalClient, TerminalClient
|
|
21
|
+
from .terminal.stream import AsyncStream, Stream
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _resolve_api_key(api_key: str | None) -> str:
|
|
25
|
+
key = api_key or os.environ.get(ENV_API_KEY)
|
|
26
|
+
if not key:
|
|
27
|
+
raise AuthError(
|
|
28
|
+
"No API key. Pass api_key=... or set the "
|
|
29
|
+
f"{ENV_API_KEY} environment variable."
|
|
30
|
+
)
|
|
31
|
+
return key
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Client:
|
|
35
|
+
"""Synchronous FxSocket client.
|
|
36
|
+
|
|
37
|
+
Usage::
|
|
38
|
+
|
|
39
|
+
from fxsocket import Client
|
|
40
|
+
|
|
41
|
+
with Client(api_key="fxs_live_...") as fx:
|
|
42
|
+
for acct in fx.accounts.list():
|
|
43
|
+
print(acct.nickname, acct.status)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
api_key: str | None = None,
|
|
49
|
+
*,
|
|
50
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
51
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
52
|
+
verify_terminal_tls: bool = True,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._api_key = _resolve_api_key(api_key)
|
|
55
|
+
self._timeout = timeout
|
|
56
|
+
#: Verify TLS for terminal calls. Private-hosting droplets use a
|
|
57
|
+
#: self-signed cert; set False (or pass a CA) to reach them.
|
|
58
|
+
self.verify_terminal_tls = verify_terminal_tls
|
|
59
|
+
self._http_client = httpx.Client(
|
|
60
|
+
base_url=base_url,
|
|
61
|
+
headers=auth_headers(self._api_key),
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
)
|
|
64
|
+
#: Account management (the v1 API).
|
|
65
|
+
self.accounts = Accounts(SyncHTTP(self._http_client))
|
|
66
|
+
self._terminals: dict[tuple[str, str], TerminalClient] = {}
|
|
67
|
+
|
|
68
|
+
def terminal(
|
|
69
|
+
self,
|
|
70
|
+
account: Account,
|
|
71
|
+
*,
|
|
72
|
+
verify: bool | None = None,
|
|
73
|
+
timeout: float | None = None,
|
|
74
|
+
) -> TerminalClient:
|
|
75
|
+
"""Return a REST client bound to ``account``'s terminal.
|
|
76
|
+
|
|
77
|
+
Resolves the endpoint from ``account.rest_url`` (shared pod or private
|
|
78
|
+
droplet). Raises :class:`TerminalNotReadyError` when the account has no
|
|
79
|
+
terminal yet (still provisioning, or bridge-only). Clients are cached
|
|
80
|
+
per endpoint and closed by :meth:`close`. Pass ``verify=False`` for a
|
|
81
|
+
private droplet's self-signed certificate.
|
|
82
|
+
"""
|
|
83
|
+
if not account.rest_url:
|
|
84
|
+
raise TerminalNotReadyError(
|
|
85
|
+
f"Account {account.id} has no terminal endpoint yet "
|
|
86
|
+
"(still provisioning, or bridge-only)."
|
|
87
|
+
)
|
|
88
|
+
key = (account.rest_url, account.platform.value)
|
|
89
|
+
cached = self._terminals.get(key)
|
|
90
|
+
if cached is None:
|
|
91
|
+
cached = TerminalClient(
|
|
92
|
+
base_url=account.rest_url,
|
|
93
|
+
api_key=self._api_key,
|
|
94
|
+
platform=account.platform,
|
|
95
|
+
verify=self.verify_terminal_tls if verify is None else verify,
|
|
96
|
+
timeout=self._timeout if timeout is None else timeout,
|
|
97
|
+
)
|
|
98
|
+
self._terminals[key] = cached
|
|
99
|
+
return cached
|
|
100
|
+
|
|
101
|
+
def stream(
|
|
102
|
+
self,
|
|
103
|
+
account: Account,
|
|
104
|
+
*,
|
|
105
|
+
verify: bool | None = None,
|
|
106
|
+
auto_reconnect: bool = True,
|
|
107
|
+
) -> Stream:
|
|
108
|
+
"""Open a synchronous WebSocket stream for ``account``.
|
|
109
|
+
|
|
110
|
+
Returns a context-managed :class:`~fxsocket.Stream`; use it as
|
|
111
|
+
``with client.stream(account) as s:`` and iterate it. Raises
|
|
112
|
+
:class:`TerminalNotReadyError` if the account has no WS endpoint yet.
|
|
113
|
+
"""
|
|
114
|
+
if not account.ws_url:
|
|
115
|
+
raise TerminalNotReadyError(
|
|
116
|
+
f"Account {account.id} has no WebSocket endpoint yet "
|
|
117
|
+
"(still provisioning, or bridge-only)."
|
|
118
|
+
)
|
|
119
|
+
verify_tls = self.verify_terminal_tls if verify is None else verify
|
|
120
|
+
api_key = self._api_key
|
|
121
|
+
platform = account.platform
|
|
122
|
+
ws_url = account.ws_url
|
|
123
|
+
|
|
124
|
+
def factory() -> AsyncStream:
|
|
125
|
+
return AsyncStream(
|
|
126
|
+
ws_url=ws_url,
|
|
127
|
+
api_key=api_key,
|
|
128
|
+
platform=platform,
|
|
129
|
+
verify=verify_tls,
|
|
130
|
+
auto_reconnect=auto_reconnect,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return Stream(factory)
|
|
134
|
+
|
|
135
|
+
def close(self) -> None:
|
|
136
|
+
try:
|
|
137
|
+
for term in self._terminals.values():
|
|
138
|
+
try:
|
|
139
|
+
term.close()
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
self._terminals.clear()
|
|
143
|
+
finally:
|
|
144
|
+
self._http_client.close()
|
|
145
|
+
|
|
146
|
+
def __enter__(self) -> Client:
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def __exit__(self, *exc: object) -> None:
|
|
150
|
+
self.close()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class AsyncClient:
|
|
154
|
+
"""Asynchronous FxSocket client.
|
|
155
|
+
|
|
156
|
+
Usage::
|
|
157
|
+
|
|
158
|
+
from fxsocket import AsyncClient
|
|
159
|
+
|
|
160
|
+
async with AsyncClient(api_key="fxs_live_...") as fx:
|
|
161
|
+
accounts = await fx.accounts.list()
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
api_key: str | None = None,
|
|
167
|
+
*,
|
|
168
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
169
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
170
|
+
verify_terminal_tls: bool = True,
|
|
171
|
+
) -> None:
|
|
172
|
+
self._api_key = _resolve_api_key(api_key)
|
|
173
|
+
self._timeout = timeout
|
|
174
|
+
self.verify_terminal_tls = verify_terminal_tls
|
|
175
|
+
self._http_client = httpx.AsyncClient(
|
|
176
|
+
base_url=base_url,
|
|
177
|
+
headers=auth_headers(self._api_key),
|
|
178
|
+
timeout=timeout,
|
|
179
|
+
)
|
|
180
|
+
self.accounts = AsyncAccounts(AsyncHTTP(self._http_client))
|
|
181
|
+
self._terminals: dict[tuple[str, str], AsyncTerminalClient] = {}
|
|
182
|
+
|
|
183
|
+
def terminal(
|
|
184
|
+
self,
|
|
185
|
+
account: Account,
|
|
186
|
+
*,
|
|
187
|
+
verify: bool | None = None,
|
|
188
|
+
timeout: float | None = None,
|
|
189
|
+
) -> AsyncTerminalClient:
|
|
190
|
+
"""Return an async REST client bound to ``account``'s terminal.
|
|
191
|
+
|
|
192
|
+
See :meth:`Client.terminal`. Clients are cached per endpoint and closed
|
|
193
|
+
by :meth:`aclose`.
|
|
194
|
+
"""
|
|
195
|
+
if not account.rest_url:
|
|
196
|
+
raise TerminalNotReadyError(
|
|
197
|
+
f"Account {account.id} has no terminal endpoint yet "
|
|
198
|
+
"(still provisioning, or bridge-only)."
|
|
199
|
+
)
|
|
200
|
+
key = (account.rest_url, account.platform.value)
|
|
201
|
+
cached = self._terminals.get(key)
|
|
202
|
+
if cached is None:
|
|
203
|
+
cached = AsyncTerminalClient(
|
|
204
|
+
base_url=account.rest_url,
|
|
205
|
+
api_key=self._api_key,
|
|
206
|
+
platform=account.platform,
|
|
207
|
+
verify=self.verify_terminal_tls if verify is None else verify,
|
|
208
|
+
timeout=self._timeout if timeout is None else timeout,
|
|
209
|
+
)
|
|
210
|
+
self._terminals[key] = cached
|
|
211
|
+
return cached
|
|
212
|
+
|
|
213
|
+
def stream(
|
|
214
|
+
self,
|
|
215
|
+
account: Account,
|
|
216
|
+
*,
|
|
217
|
+
verify: bool | None = None,
|
|
218
|
+
auto_reconnect: bool = True,
|
|
219
|
+
) -> AsyncStream:
|
|
220
|
+
"""Open an async WebSocket stream for ``account``.
|
|
221
|
+
|
|
222
|
+
Returns an :class:`~fxsocket.AsyncStream`; use it as
|
|
223
|
+
``async with client.stream(account) as s:`` and iterate it. Raises
|
|
224
|
+
:class:`TerminalNotReadyError` if the account has no WS endpoint yet.
|
|
225
|
+
"""
|
|
226
|
+
if not account.ws_url:
|
|
227
|
+
raise TerminalNotReadyError(
|
|
228
|
+
f"Account {account.id} has no WebSocket endpoint yet "
|
|
229
|
+
"(still provisioning, or bridge-only)."
|
|
230
|
+
)
|
|
231
|
+
return AsyncStream(
|
|
232
|
+
ws_url=account.ws_url,
|
|
233
|
+
api_key=self._api_key,
|
|
234
|
+
platform=account.platform,
|
|
235
|
+
verify=self.verify_terminal_tls if verify is None else verify,
|
|
236
|
+
auto_reconnect=auto_reconnect,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
async def aclose(self) -> None:
|
|
240
|
+
try:
|
|
241
|
+
for term in self._terminals.values():
|
|
242
|
+
try:
|
|
243
|
+
await term.aclose()
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
self._terminals.clear()
|
|
247
|
+
finally:
|
|
248
|
+
await self._http_client.aclose()
|
|
249
|
+
|
|
250
|
+
async def __aenter__(self) -> AsyncClient:
|
|
251
|
+
return self
|
|
252
|
+
|
|
253
|
+
async def __aexit__(self, *exc: object) -> None:
|
|
254
|
+
await self.aclose()
|
fxsocket/config.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Library-wide defaults."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
#: Base URL of the public management API (account CRUD + status).
|
|
6
|
+
DEFAULT_BASE_URL = "https://api.fxsocket.com/v1"
|
|
7
|
+
|
|
8
|
+
#: Default request timeout, in seconds, for REST calls.
|
|
9
|
+
DEFAULT_TIMEOUT = 30.0
|
|
10
|
+
|
|
11
|
+
#: Environment variable read when no ``api_key`` is passed explicitly.
|
|
12
|
+
ENV_API_KEY = "FXSOCKET_API_KEY"
|
fxsocket/enums.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Enumerations mirroring the FxSocket / terminal API vocabulary.
|
|
2
|
+
|
|
3
|
+
All are ``str`` enums, so they compare equal to the raw wire values and
|
|
4
|
+
serialize back to them unchanged.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Platform(str, Enum):
|
|
13
|
+
"""Trading platform of a linked account."""
|
|
14
|
+
|
|
15
|
+
MT4 = "mt4"
|
|
16
|
+
MT5 = "mt5"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TradingStatus(str, Enum):
|
|
20
|
+
"""Unified, public connection status of an account (v1 ``status``)."""
|
|
21
|
+
|
|
22
|
+
CONNECTED = "connected"
|
|
23
|
+
CONNECTING = "connecting"
|
|
24
|
+
DISCONNECTED = "disconnected"
|
|
25
|
+
ERROR = "error"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OrderOperation(str, Enum):
|
|
29
|
+
"""Order operation accepted by the terminal ``/OrderSend``.
|
|
30
|
+
|
|
31
|
+
All eight are accepted by both the MT4 and MT5 terminal APIs (the MT4
|
|
32
|
+
server's ``parse_operation`` maps ``BuyStopLimit``/``SellStopLimit`` too),
|
|
33
|
+
so the SDK does not gate operations by platform.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
BUY = "Buy"
|
|
37
|
+
SELL = "Sell"
|
|
38
|
+
BUY_LIMIT = "BuyLimit"
|
|
39
|
+
SELL_LIMIT = "SellLimit"
|
|
40
|
+
BUY_STOP = "BuyStop"
|
|
41
|
+
SELL_STOP = "SellStop"
|
|
42
|
+
BUY_STOP_LIMIT = "BuyStopLimit"
|
|
43
|
+
SELL_STOP_LIMIT = "SellStopLimit"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#: Operations that require a ``stop_limit_price``.
|
|
47
|
+
STOP_LIMIT_OPERATIONS = frozenset(
|
|
48
|
+
{OrderOperation.BUY_STOP_LIMIT, OrderOperation.SELL_STOP_LIMIT}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
#: Operations that are pending orders (require an entry ``price``).
|
|
52
|
+
PENDING_OPERATIONS = frozenset(
|
|
53
|
+
{
|
|
54
|
+
OrderOperation.BUY_LIMIT,
|
|
55
|
+
OrderOperation.SELL_LIMIT,
|
|
56
|
+
OrderOperation.BUY_STOP,
|
|
57
|
+
OrderOperation.SELL_STOP,
|
|
58
|
+
OrderOperation.BUY_STOP_LIMIT,
|
|
59
|
+
OrderOperation.SELL_STOP_LIMIT,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class OrderKind(str, Enum):
|
|
65
|
+
"""Whether an opened row is a live position or a resting pending order."""
|
|
66
|
+
|
|
67
|
+
POSITION = "Position"
|
|
68
|
+
PENDING = "Pending"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DealEntry(str, Enum):
|
|
72
|
+
"""Direction of a deal in trade history / the ``trades`` stream."""
|
|
73
|
+
|
|
74
|
+
IN = "In"
|
|
75
|
+
OUT = "Out"
|
|
76
|
+
IN_OUT = "InOut"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class HealthStatus(str, Enum):
|
|
80
|
+
"""Roll-up status reported by the terminal ``/status`` endpoint."""
|
|
81
|
+
|
|
82
|
+
READY = "ready"
|
|
83
|
+
STARTING = "starting"
|
|
84
|
+
DEGRADED = "degraded"
|
|
85
|
+
DOWN = "down"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Timeframe(str, Enum):
|
|
89
|
+
"""Candle timeframes.
|
|
90
|
+
|
|
91
|
+
The MT5-only members (``M2``, ``M3``, ``H2``, ``H6``, ``H8``, ``H12``)
|
|
92
|
+
are rejected client-side for MT4 accounts.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
M1 = "M1"
|
|
96
|
+
M2 = "M2"
|
|
97
|
+
M3 = "M3"
|
|
98
|
+
M5 = "M5"
|
|
99
|
+
M15 = "M15"
|
|
100
|
+
M30 = "M30"
|
|
101
|
+
H1 = "H1"
|
|
102
|
+
H2 = "H2"
|
|
103
|
+
H4 = "H4"
|
|
104
|
+
H6 = "H6"
|
|
105
|
+
H8 = "H8"
|
|
106
|
+
H12 = "H12"
|
|
107
|
+
D1 = "D1"
|
|
108
|
+
W1 = "W1"
|
|
109
|
+
MN1 = "MN1"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
#: Timeframes that only MT5 supports.
|
|
113
|
+
MT5_ONLY_TIMEFRAMES = frozenset(
|
|
114
|
+
{
|
|
115
|
+
Timeframe.M2,
|
|
116
|
+
Timeframe.M3,
|
|
117
|
+
Timeframe.H2,
|
|
118
|
+
Timeframe.H6,
|
|
119
|
+
Timeframe.H8,
|
|
120
|
+
Timeframe.H12,
|
|
121
|
+
}
|
|
122
|
+
)
|