mt5cli 1.4.0__tar.gz → 1.4.1__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.
- mt5cli-1.4.1/.agents/skills/local-qa/scripts/qa.sh +41 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.agents/skills/mt5cli/SKILL.md +2 -2
- mt5cli-1.4.1/.github/CODEOWNERS +2 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/workflows/ci.yml +11 -14
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/workflows/claude.yml +2 -2
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/workflows/release.yml +2 -2
- mt5cli-1.4.1/AGENTS.md +29 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/PKG-INFO +2 -3
- {mt5cli-1.4.0 → mt5cli-1.4.1}/README.md +0 -1
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/public-contract.md +23 -15
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/index.md +0 -1
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/__init__.py +4 -2
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/contract.py +5 -1
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/trading.py +0 -38
- {mt5cli-1.4.0 → mt5cli-1.4.1}/pyproject.toml +3 -3
- mt5cli-1.4.1/tests/cli/test_cli.bats +122 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_contracts.py +2 -2
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_examples.py +1 -1
- mt5cli-1.4.1/tests/unit/test_stable_sdk_cleanup.py +31 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_trading.py +0 -86
- {mt5cli-1.4.0 → mt5cli-1.4.1}/uv.lock +219 -13
- mt5cli-1.4.0/.agents/skills/local-qa/scripts/qa.sh +0 -26
- mt5cli-1.4.0/.agents/skills/pr-feedback-triage/SKILL.md +0 -211
- mt5cli-1.4.0/AGENTS.md +0 -37
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.claude/agents/codex.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.claude/settings.json +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/FUNDING.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/dependabot.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.github/renovate.json +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/.gitignore +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/CLAUDE.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/LICENSE +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/cli.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/client.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/converters.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/exceptions.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/grafana.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/history.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/index.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/marketdata.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/observability.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/schemas.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/telemetry.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/trading.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/docs/api/utils.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/README.md +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/compose.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/dashboards/mt5cli-market.json +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mkdocs.yml +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/__main__.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/cli.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/client.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/converters.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/exceptions.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/grafana.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/history.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/marketdata.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/observability.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/retry.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/schemas.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/telemetry.py +0 -0
- {mt5cli-1.4.0 → mt5cli-1.4.1}/mt5cli/utils.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/__init__.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/conftest.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_cli.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_client.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_grafana.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_history.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_marketdata.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_observability.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_packaging.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_telemetry.py +0 -0
- {mt5cli-1.4.0/tests → mt5cli-1.4.1/tests/unit}/test_utils.py +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euox pipefail
|
|
4
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
5
|
+
|
|
6
|
+
COOLDOWN_DAYS=7
|
|
7
|
+
export UV_EXCLUDE_NEWER="${COOLDOWN_DAYS} days"
|
|
8
|
+
export NPM_CONFIG_MIN_RELEASE_AGE="${COOLDOWN_DAYS}"
|
|
9
|
+
export PNPM_CONFIG_MINIMUM_RELEASE_AGE=$((COOLDOWN_DAYS * 24 * 60))
|
|
10
|
+
|
|
11
|
+
# Python
|
|
12
|
+
uv run ruff format .
|
|
13
|
+
uv run ruff check --fix .
|
|
14
|
+
uv run pyright .
|
|
15
|
+
uv run pytest
|
|
16
|
+
|
|
17
|
+
# Markdown and JSON
|
|
18
|
+
npx -y prettier --write './**/*.{md,json}'
|
|
19
|
+
|
|
20
|
+
# YAML
|
|
21
|
+
git ls-files -z -- '*.yml' \
|
|
22
|
+
| xargs -0 -t uvx yamllint -d '{"extends": "relaxed", "rules": {"line-length": "disable"}}'
|
|
23
|
+
|
|
24
|
+
# GitHub Actions
|
|
25
|
+
case "${OSTYPE}" in
|
|
26
|
+
darwin* | linux*)
|
|
27
|
+
# Shell scripts
|
|
28
|
+
git ls-files -z -- '*.sh' '*.bash' '*.bats' \
|
|
29
|
+
| xargs -0 -t shfmt --write --indent=2 --binary-next-line --case-indent --space-redirects
|
|
30
|
+
git ls-files -z -- '*.sh' '*.bash' '*.bats' \
|
|
31
|
+
| xargs -0 -t shellcheck
|
|
32
|
+
|
|
33
|
+
# GitHub Actions
|
|
34
|
+
zizmor --fix=safe .github/workflows
|
|
35
|
+
git ls-files -z -- '.github/workflows/*.yml' | xargs -0 -t actionlint
|
|
36
|
+
checkov --framework=all --output=github_failed_only --directory=.
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
echo 'GitHub Actions and shell script linting is only supported on Linux and macOS.'
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
@@ -12,8 +12,8 @@ command. Output format is auto-detected from the file extension (`.csv`,
|
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
MT5 terminal).
|
|
16
|
+
|
|
17
17
|
- Install: `pip install -U mt5cli MetaTrader5`.
|
|
18
18
|
- In this repo, run via `uv run mt5cli ...` or `uv run python -m mt5cli ...`.
|
|
19
19
|
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
|| (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
|
|
36
36
|
permissions:
|
|
37
37
|
contents: read
|
|
38
|
-
uses: dceoy/
|
|
38
|
+
uses: dceoy/gha-for-devops/.github/workflows/python-package-lint-and-scan.yml@main # zizmor: ignore[unpinned-uses]
|
|
39
39
|
with:
|
|
40
40
|
package-path: .
|
|
41
41
|
runs-on: windows-latest
|
|
@@ -46,32 +46,28 @@ jobs:
|
|
|
46
46
|
|| (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
|
|
47
47
|
permissions:
|
|
48
48
|
contents: read
|
|
49
|
-
uses: dceoy/
|
|
49
|
+
uses: dceoy/gha-for-devops/.github/workflows/python-package-test.yml@main # zizmor: ignore[unpinned-uses]
|
|
50
50
|
with:
|
|
51
51
|
package-path: .
|
|
52
52
|
runs-on: windows-latest
|
|
53
|
-
|
|
53
|
+
bats-test:
|
|
54
54
|
if: >
|
|
55
55
|
github.event_name == 'push'
|
|
56
56
|
|| github.event_name == 'pull_request'
|
|
57
57
|
|| (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
|
|
58
58
|
permissions:
|
|
59
59
|
contents: read
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
persist-credentials: false
|
|
65
|
-
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
66
|
-
- name: Check CLI help with minimum Click and Typer versions
|
|
67
|
-
run: uv run --with click==8.4.0 --with typer==0.26.0 mt5cli --help
|
|
60
|
+
uses: dceoy/gha-for-devops/.github/workflows/bats-test.yml@main # zizmor: ignore[unpinned-uses]
|
|
61
|
+
with:
|
|
62
|
+
search-path: ./tests/cli/
|
|
63
|
+
runs-on: windows-latest
|
|
68
64
|
python-docs-deploy:
|
|
69
65
|
if: >
|
|
70
66
|
github.event_name == 'push'
|
|
71
67
|
|| (github.event_name == 'workflow_dispatch' && inputs.workflow == 'docs-deploy')
|
|
72
68
|
permissions:
|
|
73
69
|
contents: write
|
|
74
|
-
uses: dceoy/
|
|
70
|
+
uses: dceoy/gha-for-devops/.github/workflows/python-package-mkdocs-gh-deploy.yml@main # zizmor: ignore[unpinned-uses]
|
|
75
71
|
with:
|
|
76
72
|
package-path: .
|
|
77
73
|
runs-on: ubuntu-slim
|
|
@@ -86,7 +82,7 @@ jobs:
|
|
|
86
82
|
contents: read
|
|
87
83
|
security-events: write
|
|
88
84
|
actions: read
|
|
89
|
-
uses: dceoy/
|
|
85
|
+
uses: dceoy/gha-for-devops/.github/workflows/github-codeql-analysis.yml@main # zizmor: ignore[unpinned-uses]
|
|
90
86
|
with:
|
|
91
87
|
language: >
|
|
92
88
|
["python"]
|
|
@@ -96,7 +92,8 @@ jobs:
|
|
|
96
92
|
needs:
|
|
97
93
|
- python-lint-and-scan
|
|
98
94
|
- python-test
|
|
99
|
-
|
|
95
|
+
- bats-test
|
|
96
|
+
uses: dceoy/gha-for-devops/.github/workflows/dependabot-auto-merge.yml@main # zizmor: ignore[unpinned-uses]
|
|
100
97
|
permissions:
|
|
101
98
|
contents: write
|
|
102
99
|
pull-requests: write
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
issues: write
|
|
36
36
|
id-token: write
|
|
37
37
|
actions: read
|
|
38
|
-
uses: dceoy/
|
|
38
|
+
uses: dceoy/gha-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
|
|
39
39
|
secrets:
|
|
40
40
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
|
|
41
41
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -51,7 +51,7 @@ jobs:
|
|
|
51
51
|
issues: write
|
|
52
52
|
id-token: write
|
|
53
53
|
actions: read
|
|
54
|
-
uses: dceoy/
|
|
54
|
+
uses: dceoy/gha-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses]
|
|
55
55
|
secrets:
|
|
56
56
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env]
|
|
57
57
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
permissions:
|
|
14
14
|
contents: write
|
|
15
15
|
id-token: write
|
|
16
|
-
uses: dceoy/
|
|
16
|
+
uses: dceoy/gha-for-devops/.github/workflows/python-package-release-on-pypi-and-github.yml@main # zizmor: ignore[unpinned-uses]
|
|
17
17
|
with:
|
|
18
18
|
package-path: .
|
|
19
19
|
create-github-release: true
|
|
@@ -39,6 +39,6 @@ jobs:
|
|
|
39
39
|
name: ${{ needs.build-and-release.outputs.distribution-artifact-name }}
|
|
40
40
|
path: dist/
|
|
41
41
|
- name: Publish distribution 📦 to PyPI
|
|
42
|
-
uses: pypa/gh-action-pypi-publish@
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
43
43
|
with:
|
|
44
44
|
verbose: true
|
mt5cli-1.4.1/AGENTS.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Build, Test, and Development Commands
|
|
4
|
+
|
|
5
|
+
Use `.agents/skills/local-qa/SKILL.md` for pre-handoff QA. It runs `.agents/skills/local-qa/scripts/qa.sh`, which formats, lints, type-checks, tests, formats Markdown, and checks GitHub workflows.
|
|
6
|
+
|
|
7
|
+
`bats --verbose-run ./tests/cli/` runs the Windows/MT5-oriented CLI tests against the installed `mt5cli` entry point. These require a Windows environment where `uv sync` can install MetaTrader5; state clearly when they were not run.
|
|
8
|
+
|
|
9
|
+
`skills/mt5cli/SKILL.md` documents the mt5cli agent skill.
|
|
10
|
+
|
|
11
|
+
## Coding Style & Naming Conventions
|
|
12
|
+
|
|
13
|
+
Preserve the package’s small, typed helper style rather than adding broad abstractions.
|
|
14
|
+
|
|
15
|
+
## Design Principles
|
|
16
|
+
|
|
17
|
+
Apply KISS, DRY, and YAGNI when changing code. Prefer the simplest implementation that satisfies the current CLI/API contract. Remove duplication when shared behavior is already proven by at least two concrete call sites, but avoid generic helpers for speculative reuse. Do not add configuration flags, extension hooks, or alternate backends until a real repository use case requires them.
|
|
18
|
+
|
|
19
|
+
## Testing Guidelines
|
|
20
|
+
|
|
21
|
+
Parametrize unit tests with `pytest.mark.parametrize` when the same behavior needs coverage across multiple inputs. Mock MT5/pdmt5 boundaries; do not require a live MetaTrader terminal in unit tests.
|
|
22
|
+
|
|
23
|
+
## Commit & Pull Request Guidelines
|
|
24
|
+
|
|
25
|
+
Recent history uses concise imperative commits, sometimes with conventional prefixes such as `feat:` or `chore:` and PR numbers appended by GitHub. Keep commits scoped to one logical change. Pull requests should describe behavior changes, note tests run, link related issues, and call out MT5/live-trading risk where relevant.
|
|
26
|
+
|
|
27
|
+
## Security & Configuration Tips
|
|
28
|
+
|
|
29
|
+
Never commit account credentials, broker passwords, exported private data, or local `.venv` contents. Treat `order_send` and CLI `order-send --yes` as live execution paths; gate examples and tests so they cannot place real trades accidentally.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5cli
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Generic MT5 data and execution infrastructure for Python applications
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/mt5cli.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -15,7 +15,7 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
18
|
-
Requires-Python: <3.
|
|
18
|
+
Requires-Python: <3.15,>=3.11
|
|
19
19
|
Requires-Dist: pandas>=2.2.2
|
|
20
20
|
Requires-Dist: pdmt5>=1.2.0
|
|
21
21
|
Requires-Dist: typer>=0.26.0
|
|
@@ -385,7 +385,6 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
|
|
|
385
385
|
|
|
386
386
|
## Requirements
|
|
387
387
|
|
|
388
|
-
- Python 3.11+
|
|
389
388
|
- Windows OS (MetaTrader 5 requirement)
|
|
390
389
|
- MetaTrader 5 platform installed
|
|
391
390
|
|
|
@@ -24,10 +24,11 @@ in pdmt5 — the name changed, it was not simply moved.
|
|
|
24
24
|
|
|
25
25
|
Downstream packages should import from the package root (`from mt5cli import
|
|
26
26
|
...`). The contract set `STABLE_SDK_EXPORTS` in `mt5cli.contract` enumerates
|
|
27
|
-
every package-root symbol.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
every package-root symbol. Helpers promoted for downstream use, including selected configuration and
|
|
28
|
+
parsing utilities, history timeframe resolution, and Grafana schema setup, are
|
|
29
|
+
part of that root contract. Other low-level
|
|
30
|
+
schema, export, conversion, and implementation helpers remain available only
|
|
31
|
+
from their owning modules.
|
|
31
32
|
|
|
32
33
|
### Module responsibility map
|
|
33
34
|
|
|
@@ -56,13 +57,14 @@ These names are exported from `mt5cli` and enumerated in
|
|
|
56
57
|
|
|
57
58
|
### Session lifecycle and configuration
|
|
58
59
|
|
|
59
|
-
| Symbol | Role
|
|
60
|
-
| ----------------------------------------------- |
|
|
61
|
-
| `MT5Client` | The single connected client for data, history, calculations, order checking, and order sending
|
|
62
|
-
| `build_config` | Build
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
60
|
+
| Symbol | Role |
|
|
61
|
+
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `MT5Client` | The single connected client for data, history, calculations, order checking, and order sending |
|
|
63
|
+
| `build_config` | Build the underlying MT5 connection config from connection fields; `login` accepts `int \| str \| None` — numeric strings are coerced to `int`, blank strings are treated as unset, and `${ENV_VAR}` / `$ENV_NAME` placeholders in string parameters are expanded when `allow_whole_dollar_env=True` |
|
|
64
|
+
| `substitute_mapping_values` | Generic selected-key environment substitution for downstream configuration adapters |
|
|
65
|
+
| `mt5_session` | Canonical context manager: initialize/login once, yield `MT5Client`, shut down once; a supplied `client=` remains caller-owned. |
|
|
66
|
+
| `AccountSpec` | Generic account group: symbols plus optional credentials |
|
|
67
|
+
| `resolve_account_spec`, `resolve_account_specs` | Merge overrides and expand `${ENV_VAR}` placeholders; opt-in `allow_whole_dollar_env` for bare `$NAME` |
|
|
66
68
|
|
|
67
69
|
### Closed-bar rate helpers
|
|
68
70
|
|
|
@@ -93,6 +95,11 @@ timestamp normalization in downstream apps.
|
|
|
93
95
|
See [History Collection (SQLite)](history.md) for schema, view naming, and ER
|
|
94
96
|
diagrams.
|
|
95
97
|
|
|
98
|
+
Shared package-root helpers used by downstream adapters include `Dataset`,
|
|
99
|
+
`parse_timeframe`, and `resolve_history_timeframes`. These are stable exports;
|
|
100
|
+
downstream applications should not import them from `mt5cli.utils` or
|
|
101
|
+
`mt5cli.history`.
|
|
102
|
+
|
|
96
103
|
### Trading and sizing primitives (generic)
|
|
97
104
|
|
|
98
105
|
These helpers implement broker-facing calculations only. They do not encode
|
|
@@ -108,7 +115,7 @@ strategy entries, exits, Kelly sizing, or signal logic.
|
|
|
108
115
|
| `CalibrationStatus`, `ClockStatus` | Literal status types for clock calibration and normalization |
|
|
109
116
|
| `detect_position_side` | Net long / short / flat from open positions |
|
|
110
117
|
| `calculate_spread_ratio` | Relative bid-ask spread |
|
|
111
|
-
| `calculate_margin_and_volume`, `calculate_volume_by_margin`, `
|
|
118
|
+
| `calculate_margin_and_volume`, `calculate_volume_by_margin`, `calculate_account_projected_margin_ratio` | Margin budget and volume sizing |
|
|
112
119
|
| `normalize_order_volume`, `estimate_order_margin`, `calculate_positions_margin` | Broker volume normalization and margin totals |
|
|
113
120
|
| `calculate_positions_margin_by_symbol` | Per-symbol margin map (resilient, first-seen order) |
|
|
114
121
|
| `calculate_positions_margin_safe` | Summed total margin across symbols (failed symbols skipped) |
|
|
@@ -230,8 +237,9 @@ workflows call the facade's canonical data methods (`account_info`, `positions`,
|
|
|
230
237
|
| `grafana_realized_pnl` | Cumulative realized PnL per symbol |
|
|
231
238
|
| `grafana_trade_stats` | Win/loss counts and profit per symbol |
|
|
232
239
|
|
|
233
|
-
|
|
234
|
-
|
|
240
|
+
`ensure_grafana_schema` is a stable package-root helper for downstream setup.
|
|
241
|
+
Other lower-level helpers (`create_grafana_views`, `create_grafana_indexes`,
|
|
242
|
+
`create_snapshot_tables`, `start_snapshot_run`,
|
|
235
243
|
`insert_account_snapshot`, `insert_position_snapshots`, `insert_order_snapshots`,
|
|
236
244
|
`insert_terminal_snapshot`, `record_snapshot_run`) are available directly from
|
|
237
245
|
`mt5cli.grafana` and are not part of the package-root stable surface.
|
|
@@ -367,7 +375,7 @@ their own adapter layer.
|
|
|
367
375
|
|
|
368
376
|
## Contract verification
|
|
369
377
|
|
|
370
|
-
`tests/test_contracts.py` asserts that every name in `STABLE_SDK_EXPORTS` is
|
|
378
|
+
`tests/unit/test_contracts.py` asserts that every name in `STABLE_SDK_EXPORTS` is
|
|
371
379
|
importable from `mt5cli`, that all package-root exports are covered by the
|
|
372
380
|
stable set, and documents key closed-bar, SQLite loading, account-resolution,
|
|
373
381
|
and trading-session behaviors.
|
|
@@ -9,7 +9,7 @@ strategy responsibilities.
|
|
|
9
9
|
|
|
10
10
|
from importlib.metadata import version
|
|
11
11
|
|
|
12
|
-
from .client import MT5Client, build_config, mt5_session
|
|
12
|
+
from .client import MT5Client, build_config, mt5_session, substitute_mapping_values
|
|
13
13
|
from .contract import STABLE_SDK_EXPORTS
|
|
14
14
|
from .exceptions import (
|
|
15
15
|
Mt5CliError,
|
|
@@ -17,6 +17,7 @@ from .exceptions import (
|
|
|
17
17
|
Mt5OperationError,
|
|
18
18
|
Mt5SchemaError,
|
|
19
19
|
)
|
|
20
|
+
from .grafana import ensure_grafana_schema
|
|
20
21
|
from .history import (
|
|
21
22
|
RateTarget,
|
|
22
23
|
ThrottledHistoryUpdater,
|
|
@@ -26,6 +27,7 @@ from .history import (
|
|
|
26
27
|
load_rate_series_by_granularity,
|
|
27
28
|
load_rate_series_from_sqlite,
|
|
28
29
|
report_rate_gaps,
|
|
30
|
+
resolve_history_timeframes,
|
|
29
31
|
update_history,
|
|
30
32
|
update_history_with_config,
|
|
31
33
|
)
|
|
@@ -56,7 +58,6 @@ from .trading import (
|
|
|
56
58
|
TickClockNormalizer,
|
|
57
59
|
calculate_account_projected_margin_ratio,
|
|
58
60
|
calculate_margin_and_volume,
|
|
59
|
-
calculate_new_position_margin_ratio,
|
|
60
61
|
calculate_positions_margin,
|
|
61
62
|
calculate_positions_margin_by_symbol,
|
|
62
63
|
calculate_positions_margin_safe,
|
|
@@ -82,6 +83,7 @@ from .trading import (
|
|
|
82
83
|
update_sltp_for_open_positions,
|
|
83
84
|
update_trailing_stop_loss_for_open_positions,
|
|
84
85
|
)
|
|
86
|
+
from .utils import Dataset, parse_timeframe
|
|
85
87
|
|
|
86
88
|
__version__ = version(__package__) if __package__ else None
|
|
87
89
|
|
|
@@ -123,11 +123,11 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
|
|
123
123
|
"TickClockNormalizer",
|
|
124
124
|
"RateTarget",
|
|
125
125
|
"ThrottledHistoryUpdater",
|
|
126
|
+
"Dataset",
|
|
126
127
|
"build_config",
|
|
127
128
|
"build_rate_targets",
|
|
128
129
|
"calculate_account_projected_margin_ratio",
|
|
129
130
|
"calculate_margin_and_volume",
|
|
130
|
-
"calculate_new_position_margin_ratio",
|
|
131
131
|
"calculate_projected_margin_ratio",
|
|
132
132
|
"calculate_positions_margin",
|
|
133
133
|
"calculate_positions_margin_by_symbol",
|
|
@@ -144,6 +144,7 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
|
|
144
144
|
"detect_position_side",
|
|
145
145
|
"determine_order_limits",
|
|
146
146
|
"drop_forming_rate_bar",
|
|
147
|
+
"ensure_grafana_schema",
|
|
147
148
|
"ensure_symbol_selected",
|
|
148
149
|
"estimate_order_margin",
|
|
149
150
|
"extract_tick_price",
|
|
@@ -157,11 +158,14 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
|
|
157
158
|
"load_rate_series_from_sqlite",
|
|
158
159
|
"mt5_session",
|
|
159
160
|
"normalize_order_volume",
|
|
161
|
+
"parse_timeframe",
|
|
160
162
|
"place_market_order",
|
|
161
163
|
"report_rate_gaps",
|
|
162
164
|
"resolve_broker_filling_mode",
|
|
163
165
|
"resolve_account_spec",
|
|
164
166
|
"resolve_account_specs",
|
|
167
|
+
"resolve_history_timeframes",
|
|
168
|
+
"substitute_mapping_values",
|
|
165
169
|
"update_history",
|
|
166
170
|
"update_history_with_config",
|
|
167
171
|
"update_observability",
|
|
@@ -310,7 +310,6 @@ __all__ = [
|
|
|
310
310
|
"TickClockNormalizer",
|
|
311
311
|
"calculate_account_projected_margin_ratio",
|
|
312
312
|
"calculate_margin_and_volume",
|
|
313
|
-
"calculate_new_position_margin_ratio",
|
|
314
313
|
"calculate_positions_margin",
|
|
315
314
|
"calculate_positions_margin_by_symbol",
|
|
316
315
|
"calculate_positions_margin_safe",
|
|
@@ -2269,43 +2268,6 @@ def calculate_spread_ratio(client: _Mt5ClientProtocol, symbol: str) -> float:
|
|
|
2269
2268
|
return (ask - bid) / ((ask + bid) / 2.0)
|
|
2270
2269
|
|
|
2271
2270
|
|
|
2272
|
-
def calculate_new_position_margin_ratio(
|
|
2273
|
-
client: _Mt5ClientProtocol,
|
|
2274
|
-
*,
|
|
2275
|
-
symbol: str,
|
|
2276
|
-
new_position_side: OrderSide | None = None,
|
|
2277
|
-
new_position_volume: float = 0.0,
|
|
2278
|
-
) -> float:
|
|
2279
|
-
"""Return total margin/equity ratio after an optional hypothetical position.
|
|
2280
|
-
|
|
2281
|
-
Missing or falsy account ``equity``/``margin`` values are normalized to
|
|
2282
|
-
``0.0``, and the hypothetical order margin from ``order_calc_margin()`` is
|
|
2283
|
-
accepted as-is without a positivity check.
|
|
2284
|
-
|
|
2285
|
-
Raises:
|
|
2286
|
-
Mt5OperationError: If equity or required tick data is invalid.
|
|
2287
|
-
"""
|
|
2288
|
-
account = get_account_snapshot(client)
|
|
2289
|
-
equity = float(account.get("equity") or 0.0)
|
|
2290
|
-
if equity <= 0:
|
|
2291
|
-
msg = "Account equity must be positive to calculate margin ratio."
|
|
2292
|
-
raise Mt5OperationError(msg)
|
|
2293
|
-
margin = float(account.get("margin") or 0.0)
|
|
2294
|
-
if new_position_side is not None and new_position_volume > 0:
|
|
2295
|
-
side = _normalize_order_side(new_position_side)
|
|
2296
|
-
price = extract_tick_price(
|
|
2297
|
-
get_tick_snapshot(client, symbol), "ask" if side == "BUY" else "bid"
|
|
2298
|
-
)
|
|
2299
|
-
price = _require_tick_price(price, symbol)
|
|
2300
|
-
order_type = (
|
|
2301
|
-
client.mt5.ORDER_TYPE_BUY if side == "BUY" else client.mt5.ORDER_TYPE_SELL
|
|
2302
|
-
)
|
|
2303
|
-
margin += float(
|
|
2304
|
-
client.order_calc_margin(order_type, symbol, new_position_volume, price),
|
|
2305
|
-
)
|
|
2306
|
-
return margin / equity
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
2271
|
def _account_equity(client: _Mt5ClientProtocol) -> float:
|
|
2310
2272
|
account = get_account_snapshot(client)
|
|
2311
2273
|
return _required_account_number(account, "equity", allow_zero=False)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mt5cli"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.1"
|
|
4
4
|
description = "Generic MT5 data and execution infrastructure for Python applications"
|
|
5
5
|
authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
6
6
|
maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
|
7
7
|
license = "MIT"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">= 3.11, < 3.
|
|
10
|
+
requires-python = ">= 3.11, < 3.15"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"pdmt5 >= 1.2.0",
|
|
13
13
|
"pandas >= 2.2.2",
|
|
@@ -175,7 +175,7 @@ addopts = [
|
|
|
175
175
|
"--capture=no",
|
|
176
176
|
]
|
|
177
177
|
pythonpath = ["."]
|
|
178
|
-
testpaths = ["tests"]
|
|
178
|
+
testpaths = ["tests/unit"]
|
|
179
179
|
python_files = ["test_*.py", "*_test.py"]
|
|
180
180
|
python_classes = ["Test*"]
|
|
181
181
|
python_functions = ["test_*"]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# CLI integration tests for the installed mt5cli entry point.
|
|
4
|
+
#
|
|
5
|
+
# These cases only exercise paths that never connect to MetaTrader 5: help
|
|
6
|
+
# output, SQLite-only commands, and validation failures. No case passes --yes,
|
|
7
|
+
# so the suite can never place a live trade.
|
|
8
|
+
|
|
9
|
+
setup_file() {
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
uv sync
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
teardown_file() {
|
|
15
|
+
:
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
seed_rate_database() {
|
|
19
|
+
local database="$1"
|
|
20
|
+
cat > "${BATS_TEST_TMPDIR}/seed_rates.py" << 'PY'
|
|
21
|
+
import sqlite3
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
with sqlite3.connect(sys.argv[1]) as conn:
|
|
25
|
+
conn.execute('CREATE TABLE "rate_EURUSD__M1_1"(time TEXT, close REAL)')
|
|
26
|
+
conn.executemany(
|
|
27
|
+
'INSERT INTO "rate_EURUSD__M1_1"(time, close) VALUES (?, ?)',
|
|
28
|
+
[("2024-01-01T00:00:00+00:00", 1.0), ("2024-01-01T00:02:00+00:00", 1.1)],
|
|
29
|
+
)
|
|
30
|
+
PY
|
|
31
|
+
uv run python "${BATS_TEST_TMPDIR}/seed_rates.py" "${database}"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@test "pass with \"mt5cli --help\"" {
|
|
35
|
+
run uv run mt5cli --help
|
|
36
|
+
[[ "${status}" -eq 0 ]]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@test "pass with \"mt5cli collect-history --help\"" {
|
|
40
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/history.db" collect-history --help
|
|
41
|
+
[[ "${status}" -eq 0 ]]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "pass with \"mt5cli order-send --help\"" {
|
|
45
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/out.csv" order-send --help
|
|
46
|
+
[[ "${status}" -eq 0 ]]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@test "pass with \"mt5cli grafana-schema\"" {
|
|
50
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/grafana.db" grafana-schema
|
|
51
|
+
[[ "${status}" -eq 0 ]]
|
|
52
|
+
[[ -f "${BATS_TEST_TMPDIR}/grafana.db" ]]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@test "pass with \"mt5cli grafana-schema\" run twice" {
|
|
56
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/grafana.db" grafana-schema
|
|
57
|
+
[[ "${status}" -eq 0 ]]
|
|
58
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/grafana.db" grafana-schema
|
|
59
|
+
[[ "${status}" -eq 0 ]]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@test "pass with \"mt5cli grafana-schema --publish-copy\"" {
|
|
63
|
+
run uv run mt5cli \
|
|
64
|
+
--output "${BATS_TEST_TMPDIR}/grafana.db" \
|
|
65
|
+
grafana-schema \
|
|
66
|
+
--publish-copy "${BATS_TEST_TMPDIR}/published.db"
|
|
67
|
+
[[ "${status}" -eq 0 ]]
|
|
68
|
+
[[ -f "${BATS_TEST_TMPDIR}/published.db" ]]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@test "pass with \"mt5cli history-gaps\"" {
|
|
72
|
+
seed_rate_database "${BATS_TEST_TMPDIR}/history.db"
|
|
73
|
+
run uv run mt5cli \
|
|
74
|
+
--output "${BATS_TEST_TMPDIR}/gaps.json" \
|
|
75
|
+
history-gaps \
|
|
76
|
+
--sqlite3 "${BATS_TEST_TMPDIR}/history.db"
|
|
77
|
+
[[ "${status}" -eq 0 ]]
|
|
78
|
+
[[ -f "${BATS_TEST_TMPDIR}/gaps.json" ]]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@test "fail with \"mt5cli history-gaps\" for a missing database" {
|
|
82
|
+
run uv run mt5cli \
|
|
83
|
+
--output "${BATS_TEST_TMPDIR}/gaps.json" \
|
|
84
|
+
history-gaps \
|
|
85
|
+
--sqlite3 "${BATS_TEST_TMPDIR}/no-such.db"
|
|
86
|
+
[[ "${status}" -ne 0 ]]
|
|
87
|
+
[[ ! -e "${BATS_TEST_TMPDIR}/no-such.db" ]]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@test "fail with \"mt5cli order-send\" without --yes" {
|
|
91
|
+
run uv run mt5cli \
|
|
92
|
+
--output "${BATS_TEST_TMPDIR}/out.csv" \
|
|
93
|
+
order-send \
|
|
94
|
+
--request '{"action": 1, "symbol": "EURUSD"}'
|
|
95
|
+
[[ "${status}" -ne 0 ]]
|
|
96
|
+
[[ ! -e "${BATS_TEST_TMPDIR}/out.csv" ]]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@test "fail with \"mt5cli close-positions\" without a symbol or ticket" {
|
|
100
|
+
run uv run mt5cli \
|
|
101
|
+
--output "${BATS_TEST_TMPDIR}/close.json" \
|
|
102
|
+
close-positions
|
|
103
|
+
[[ "${status}" -ne 0 ]]
|
|
104
|
+
[[ ! -e "${BATS_TEST_TMPDIR}/close.json" ]]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@test "fail with \"mt5cli collect-history\" without SQLite3 output" {
|
|
108
|
+
run uv run mt5cli \
|
|
109
|
+
--output "${BATS_TEST_TMPDIR}/history.csv" \
|
|
110
|
+
collect-history \
|
|
111
|
+
--symbol EURUSD \
|
|
112
|
+
--date-from 2024-01-01 \
|
|
113
|
+
--date-to 2024-02-01
|
|
114
|
+
[[ "${status}" -ne 0 ]]
|
|
115
|
+
[[ ! -e "${BATS_TEST_TMPDIR}/history.csv" ]]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@test "fail with an undetectable output format" {
|
|
119
|
+
run uv run mt5cli --output "${BATS_TEST_TMPDIR}/out.xyz" account-info
|
|
120
|
+
[[ "${status}" -ne 0 ]]
|
|
121
|
+
[[ ! -e "${BATS_TEST_TMPDIR}/out.xyz" ]]
|
|
122
|
+
}
|
|
@@ -526,7 +526,7 @@ class TestStableSdkContract:
|
|
|
526
526
|
def test_documented_contract_identifies_runtime_export_set(self) -> None:
|
|
527
527
|
"""The public-contract document points to the same runtime authority."""
|
|
528
528
|
contract_doc = (
|
|
529
|
-
Path(__file__).parents[
|
|
529
|
+
Path(__file__).parents[2] / "docs" / "api" / "public-contract.md"
|
|
530
530
|
).read_text(encoding="utf-8")
|
|
531
531
|
assert "`mt5cli.STABLE_SDK_EXPORTS`" in contract_doc
|
|
532
532
|
assert set(mt5cli.__all__) - {"STABLE_SDK_EXPORTS"} == set(STABLE_SDK_EXPORTS)
|
|
@@ -548,7 +548,7 @@ class TestStableSdkContract:
|
|
|
548
548
|
],
|
|
549
549
|
)
|
|
550
550
|
def test_pdmt5_pass_through_names_stay_out_of_public_contract(name: str) -> None:
|
|
551
|
-
"""
|
|
551
|
+
"""Low-level pdmt5 types/constants stay out of the stable package root.
|
|
552
552
|
|
|
553
553
|
docs/api/public-contract.md documents that mt5cli is not a pass-through
|
|
554
554
|
compatibility namespace for pdmt5; these names must never reappear on the
|
|
@@ -11,7 +11,7 @@ import pytest
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from collections.abc import Iterator
|
|
13
13
|
|
|
14
|
-
_EXAMPLES_DIR = Path(__file__).
|
|
14
|
+
_EXAMPLES_DIR = Path(__file__).parents[2] / "examples" / "grafana"
|
|
15
15
|
_DASHBOARDS_DIR = _EXAMPLES_DIR / "dashboards"
|
|
16
16
|
|
|
17
17
|
|