open-api-mt5 0.7.2__tar.gz → 0.7.3__tar.gz
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.
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/PKG-INFO +1 -1
- open_api_mt5-0.7.3/app/services/connection_service.py +72 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/PKG-INFO +1 -1
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/pyproject.toml +1 -1
- open_api_mt5-0.7.3/tests/test_connection_service.py +100 -0
- open_api_mt5-0.7.2/app/services/connection_service.py +0 -41
- open_api_mt5-0.7.2/tests/test_connection_service.py +0 -50
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/README.md +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/__init__.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/cli.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/main.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/__init__.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/account_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/bar_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/calendar_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/config_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/exposure_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/market_data_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/news_close_store.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/registry_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/serialization.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/stream_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/symbol_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/ticket_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/app/services/trade_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/SOURCES.txt +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/dependency_links.txt +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/entry_points.txt +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/requires.txt +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/open_api_mt5.egg-info/top_level.txt +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/setup.cfg +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_bar_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_bars_api.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_calendar_api.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_calendar_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_config_service.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_trade_api.py +0 -0
- {open_api_mt5-0.7.2 → open_api_mt5-0.7.3}/tests/test_trade_service.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePath, PureWindowsPath
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import MetaTrader5 as mt5
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _path_parts(value: str) -> PurePath:
|
|
10
|
+
if "\\" in value or ":" in value:
|
|
11
|
+
return PureWindowsPath(value)
|
|
12
|
+
return PurePath(value)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _normalize_terminal_dir(value: str | None) -> str | None:
|
|
16
|
+
if not value:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
cleaned = value.strip().strip('"')
|
|
20
|
+
if not cleaned:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
parsed = _path_parts(cleaned)
|
|
24
|
+
if parsed.name.lower() in {"terminal.exe", "terminal64.exe"}:
|
|
25
|
+
parsed = parsed.parent
|
|
26
|
+
return str(parsed).replace("/", "\\").rstrip("\\").casefold()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _connected_to_requested_terminal(path: str) -> bool:
|
|
30
|
+
terminal_info = mt5.terminal_info()
|
|
31
|
+
connected_path = _normalize_terminal_dir(getattr(terminal_info, "path", None))
|
|
32
|
+
requested_path = _normalize_terminal_dir(path)
|
|
33
|
+
return connected_path is None or requested_path is None or connected_path == requested_path
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def initialize_connection(
|
|
37
|
+
username: str | int | None,
|
|
38
|
+
password: str | None,
|
|
39
|
+
server: str | None,
|
|
40
|
+
path: str | None = None,
|
|
41
|
+
portable: bool | None = None,
|
|
42
|
+
) -> tuple[bool, str]:
|
|
43
|
+
init_kwargs: dict[str, Any] = {}
|
|
44
|
+
if path:
|
|
45
|
+
init_kwargs["portable"] = True if portable is None else portable
|
|
46
|
+
elif portable is not None:
|
|
47
|
+
init_kwargs["portable"] = portable
|
|
48
|
+
if username not in (None, ""):
|
|
49
|
+
try:
|
|
50
|
+
init_kwargs["login"] = int(username)
|
|
51
|
+
except ValueError:
|
|
52
|
+
return False, "failed: username/login must be a number"
|
|
53
|
+
if password:
|
|
54
|
+
init_kwargs["password"] = password
|
|
55
|
+
if server:
|
|
56
|
+
init_kwargs["server"] = server
|
|
57
|
+
|
|
58
|
+
initialized = mt5.initialize(path, **init_kwargs) if path else mt5.initialize(**init_kwargs)
|
|
59
|
+
if initialized:
|
|
60
|
+
if path and not _connected_to_requested_terminal(path):
|
|
61
|
+
connected_path = getattr(mt5.terminal_info(), "path", "unknown")
|
|
62
|
+
mt5.shutdown()
|
|
63
|
+
return False, f"failed: connected to terminal at '{connected_path}' instead of requested path '{path}'"
|
|
64
|
+
return True, "connected"
|
|
65
|
+
return False, f"failed: {mt5.last_error()}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def shutdown_connection() -> tuple[bool, str]:
|
|
69
|
+
mt5.shutdown()
|
|
70
|
+
if mt5.terminal_info() is None:
|
|
71
|
+
return True, "disconnected"
|
|
72
|
+
return False, "failed: terminal still connected"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
from app.services import connection_service
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_initialize_connection_uses_portable_mode_with_terminal_path(monkeypatch):
|
|
9
|
+
captured = {"args": None, "kwargs": None}
|
|
10
|
+
|
|
11
|
+
def initialize(*args, **kwargs):
|
|
12
|
+
captured["args"] = args
|
|
13
|
+
captured["kwargs"] = kwargs
|
|
14
|
+
return True
|
|
15
|
+
|
|
16
|
+
monkeypatch.setattr(connection_service.mt5, "initialize", initialize, raising=False)
|
|
17
|
+
monkeypatch.setattr(
|
|
18
|
+
connection_service.mt5,
|
|
19
|
+
"terminal_info",
|
|
20
|
+
lambda: SimpleNamespace(path=r"C:\MT5-A"),
|
|
21
|
+
raising=False,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
assert connection_service.initialize_connection(
|
|
25
|
+
username="123",
|
|
26
|
+
password="secret",
|
|
27
|
+
server="Broker-Server",
|
|
28
|
+
path=r"C:\MT5-A\terminal64.exe",
|
|
29
|
+
) == (True, "connected")
|
|
30
|
+
assert captured["args"] == (r"C:\MT5-A\terminal64.exe",)
|
|
31
|
+
assert captured["kwargs"] == {
|
|
32
|
+
"portable": True,
|
|
33
|
+
"login": 123,
|
|
34
|
+
"password": "secret",
|
|
35
|
+
"server": "Broker-Server",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_initialize_connection_allows_disabling_portable_mode(monkeypatch):
|
|
40
|
+
captured = {"args": None, "kwargs": None}
|
|
41
|
+
|
|
42
|
+
def initialize(*args, **kwargs):
|
|
43
|
+
captured["args"] = args
|
|
44
|
+
captured["kwargs"] = kwargs
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
monkeypatch.setattr(
|
|
48
|
+
connection_service.mt5,
|
|
49
|
+
"initialize",
|
|
50
|
+
initialize,
|
|
51
|
+
raising=False,
|
|
52
|
+
)
|
|
53
|
+
monkeypatch.setattr(
|
|
54
|
+
connection_service.mt5,
|
|
55
|
+
"terminal_info",
|
|
56
|
+
lambda: SimpleNamespace(path=r"C:\MT5-A"),
|
|
57
|
+
raising=False,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
assert connection_service.initialize_connection(
|
|
61
|
+
username="123",
|
|
62
|
+
password=None,
|
|
63
|
+
server=None,
|
|
64
|
+
path=r"C:\MT5-A\terminal64.exe",
|
|
65
|
+
portable=False,
|
|
66
|
+
) == (True, "connected")
|
|
67
|
+
assert captured["args"] == (r"C:\MT5-A\terminal64.exe",)
|
|
68
|
+
assert captured["kwargs"] == {
|
|
69
|
+
"portable": False,
|
|
70
|
+
"login": 123,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_initialize_connection_rejects_wrong_running_terminal(monkeypatch):
|
|
75
|
+
shutdown_called = False
|
|
76
|
+
|
|
77
|
+
monkeypatch.setattr(connection_service.mt5, "initialize", lambda *_args, **_kwargs: True, raising=False)
|
|
78
|
+
monkeypatch.setattr(
|
|
79
|
+
connection_service.mt5,
|
|
80
|
+
"terminal_info",
|
|
81
|
+
lambda: SimpleNamespace(path=r"C:\MT5-B"),
|
|
82
|
+
raising=False,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def shutdown():
|
|
86
|
+
nonlocal shutdown_called
|
|
87
|
+
shutdown_called = True
|
|
88
|
+
|
|
89
|
+
monkeypatch.setattr(connection_service.mt5, "shutdown", shutdown, raising=False)
|
|
90
|
+
|
|
91
|
+
ok, message = connection_service.initialize_connection(
|
|
92
|
+
username="123",
|
|
93
|
+
password=None,
|
|
94
|
+
server=None,
|
|
95
|
+
path=r"C:\MT5-A\terminal64.exe",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
assert ok is False
|
|
99
|
+
assert "instead of requested path" in message
|
|
100
|
+
assert shutdown_called is True
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import MetaTrader5 as mt5
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def initialize_connection(
|
|
9
|
-
username: str | int | None,
|
|
10
|
-
password: str | None,
|
|
11
|
-
server: str | None,
|
|
12
|
-
path: str | None = None,
|
|
13
|
-
portable: bool | None = None,
|
|
14
|
-
) -> tuple[bool, str]:
|
|
15
|
-
init_kwargs: dict[str, Any] = {}
|
|
16
|
-
if path:
|
|
17
|
-
init_kwargs["path"] = path
|
|
18
|
-
init_kwargs["portable"] = True if portable is None else portable
|
|
19
|
-
elif portable is not None:
|
|
20
|
-
init_kwargs["portable"] = portable
|
|
21
|
-
if username not in (None, ""):
|
|
22
|
-
try:
|
|
23
|
-
init_kwargs["login"] = int(username)
|
|
24
|
-
except ValueError:
|
|
25
|
-
return False, "failed: username/login must be a number"
|
|
26
|
-
if password:
|
|
27
|
-
init_kwargs["password"] = password
|
|
28
|
-
if server:
|
|
29
|
-
init_kwargs["server"] = server
|
|
30
|
-
|
|
31
|
-
initialized = mt5.initialize(**init_kwargs)
|
|
32
|
-
if initialized:
|
|
33
|
-
return True, "connected"
|
|
34
|
-
return False, f"failed: {mt5.last_error()}"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def shutdown_connection() -> tuple[bool, str]:
|
|
38
|
-
mt5.shutdown()
|
|
39
|
-
if mt5.terminal_info() is None:
|
|
40
|
-
return True, "disconnected"
|
|
41
|
-
return False, "failed: terminal still connected"
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from app.services import connection_service
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_initialize_connection_uses_portable_mode_with_terminal_path(monkeypatch):
|
|
7
|
-
captured = {}
|
|
8
|
-
|
|
9
|
-
def initialize(**kwargs):
|
|
10
|
-
captured.update(kwargs)
|
|
11
|
-
return True
|
|
12
|
-
|
|
13
|
-
monkeypatch.setattr(connection_service.mt5, "initialize", initialize, raising=False)
|
|
14
|
-
|
|
15
|
-
assert connection_service.initialize_connection(
|
|
16
|
-
username="123",
|
|
17
|
-
password="secret",
|
|
18
|
-
server="Broker-Server",
|
|
19
|
-
path=r"C:\MT5-A\terminal64.exe",
|
|
20
|
-
) == (True, "connected")
|
|
21
|
-
assert captured == {
|
|
22
|
-
"path": r"C:\MT5-A\terminal64.exe",
|
|
23
|
-
"portable": True,
|
|
24
|
-
"login": 123,
|
|
25
|
-
"password": "secret",
|
|
26
|
-
"server": "Broker-Server",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_initialize_connection_allows_disabling_portable_mode(monkeypatch):
|
|
31
|
-
captured = {}
|
|
32
|
-
monkeypatch.setattr(
|
|
33
|
-
connection_service.mt5,
|
|
34
|
-
"initialize",
|
|
35
|
-
lambda **kwargs: captured.update(kwargs) or True,
|
|
36
|
-
raising=False,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
assert connection_service.initialize_connection(
|
|
40
|
-
username="123",
|
|
41
|
-
password=None,
|
|
42
|
-
server=None,
|
|
43
|
-
path=r"C:\MT5-A\terminal64.exe",
|
|
44
|
-
portable=False,
|
|
45
|
-
) == (True, "connected")
|
|
46
|
-
assert captured == {
|
|
47
|
-
"path": r"C:\MT5-A\terminal64.exe",
|
|
48
|
-
"portable": False,
|
|
49
|
-
"login": 123,
|
|
50
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|