mt5cli 1.4.0__tar.gz → 1.4.2__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.
Files changed (86) hide show
  1. mt5cli-1.4.2/.agents/skills/local-qa/scripts/qa.sh +42 -0
  2. {mt5cli-1.4.0 → mt5cli-1.4.2}/.agents/skills/mt5cli/SKILL.md +6 -4
  3. mt5cli-1.4.2/.github/CODEOWNERS +2 -0
  4. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/workflows/ci.yml +11 -14
  5. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/workflows/claude.yml +2 -2
  6. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/workflows/release.yml +2 -2
  7. mt5cli-1.4.2/AGENTS.md +29 -0
  8. {mt5cli-1.4.0 → mt5cli-1.4.2}/PKG-INFO +21 -15
  9. {mt5cli-1.4.0 → mt5cli-1.4.2}/README.md +19 -13
  10. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/history.md +43 -109
  11. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/public-contract.md +61 -57
  12. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/trading.md +23 -17
  13. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/index.md +16 -10
  14. mt5cli-1.4.2/mt5cli/__init__.py +18 -0
  15. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/cli.py +39 -52
  16. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/client.py +24 -23
  17. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/contract.py +6 -76
  18. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/converters.py +70 -14
  19. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/history.py +209 -687
  20. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/marketdata.py +1 -1
  21. mt5cli-1.4.2/mt5cli/rates.py +538 -0
  22. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/schemas.py +102 -44
  23. mt5cli-1.4.0/mt5cli/__init__.py → mt5cli-1.4.2/mt5cli/sdk.py +19 -22
  24. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/trading.py +6 -44
  25. {mt5cli-1.4.0 → mt5cli-1.4.2}/pyproject.toml +5 -5
  26. mt5cli-1.4.2/tests/cli/test_cli.bats +127 -0
  27. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_cli.py +37 -110
  28. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_client.py +105 -29
  29. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_contracts.py +120 -29
  30. mt5cli-1.4.2/tests/unit/test_docs_contract.py +40 -0
  31. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_examples.py +1 -1
  32. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_history.py +588 -2013
  33. mt5cli-1.4.2/tests/unit/test_history_contract.py +371 -0
  34. mt5cli-1.4.2/tests/unit/test_history_edge_contract.py +122 -0
  35. mt5cli-1.4.2/tests/unit/test_rate_timestamp_contract.py +291 -0
  36. mt5cli-1.4.2/tests/unit/test_rates.py +380 -0
  37. mt5cli-1.4.2/tests/unit/test_rates_contract.py +220 -0
  38. mt5cli-1.4.2/tests/unit/test_stable_sdk_cleanup.py +52 -0
  39. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_trading.py +0 -86
  40. {mt5cli-1.4.0 → mt5cli-1.4.2}/uv.lock +219 -13
  41. mt5cli-1.4.0/.agents/skills/local-qa/scripts/qa.sh +0 -26
  42. mt5cli-1.4.0/.agents/skills/pr-feedback-triage/SKILL.md +0 -211
  43. mt5cli-1.4.0/AGENTS.md +0 -37
  44. {mt5cli-1.4.0 → mt5cli-1.4.2}/.agents/skills/local-qa/SKILL.md +0 -0
  45. {mt5cli-1.4.0 → mt5cli-1.4.2}/.claude/agents/codex.md +0 -0
  46. {mt5cli-1.4.0 → mt5cli-1.4.2}/.claude/settings.json +0 -0
  47. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/FUNDING.yml +0 -0
  48. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/dependabot.yml +0 -0
  49. {mt5cli-1.4.0 → mt5cli-1.4.2}/.github/renovate.json +0 -0
  50. {mt5cli-1.4.0 → mt5cli-1.4.2}/.gitignore +0 -0
  51. {mt5cli-1.4.0 → mt5cli-1.4.2}/CLAUDE.md +0 -0
  52. {mt5cli-1.4.0 → mt5cli-1.4.2}/LICENSE +0 -0
  53. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/cli.md +0 -0
  54. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/client.md +0 -0
  55. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/converters.md +0 -0
  56. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/exceptions.md +0 -0
  57. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/grafana.md +0 -0
  58. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/index.md +0 -0
  59. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/marketdata.md +0 -0
  60. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/observability.md +0 -0
  61. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/schemas.md +0 -0
  62. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/telemetry.md +0 -0
  63. {mt5cli-1.4.0 → mt5cli-1.4.2}/docs/api/utils.md +0 -0
  64. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/README.md +0 -0
  65. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/compose.yml +0 -0
  66. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  67. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  68. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  69. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  70. {mt5cli-1.4.0 → mt5cli-1.4.2}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  71. {mt5cli-1.4.0 → mt5cli-1.4.2}/mkdocs.yml +0 -0
  72. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/__main__.py +0 -0
  73. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/exceptions.py +0 -0
  74. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/grafana.py +0 -0
  75. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/observability.py +0 -0
  76. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/retry.py +0 -0
  77. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/telemetry.py +0 -0
  78. {mt5cli-1.4.0 → mt5cli-1.4.2}/mt5cli/utils.py +0 -0
  79. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/__init__.py +0 -0
  80. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/conftest.py +0 -0
  81. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_grafana.py +0 -0
  82. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_marketdata.py +0 -0
  83. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_observability.py +0 -0
  84. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_packaging.py +0 -0
  85. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_telemetry.py +0 -0
  86. {mt5cli-1.4.0/tests → mt5cli-1.4.2/tests/unit}/test_utils.py +0 -0
@@ -0,0 +1,42 @@
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 sync
13
+ uv run ruff format .
14
+ uv run ruff check --fix .
15
+ uv run pyright .
16
+ uv run pytest
17
+
18
+ # Markdown and JSON
19
+ npx -y prettier --write './**/*.{md,json}'
20
+
21
+ # YAML
22
+ git ls-files -z -- '*.yml' \
23
+ | xargs -0 -t uvx yamllint -d '{"extends": "relaxed", "rules": {"line-length": "disable"}}'
24
+
25
+ # GitHub Actions
26
+ case "${OSTYPE}" in
27
+ darwin* | linux*)
28
+ # Shell scripts
29
+ git ls-files -z -- '*.sh' '*.bash' '*.bats' \
30
+ | xargs -0 -t shfmt --write --indent=2 --binary-next-line --case-indent --space-redirects
31
+ git ls-files -z -- '*.sh' '*.bash' '*.bats' \
32
+ | xargs -0 -t shellcheck
33
+
34
+ # GitHub Actions
35
+ zizmor --fix=safe .github/workflows
36
+ git ls-files -z -- '.github/workflows/*.yml' | xargs -0 -t actionlint
37
+ checkov --framework=all --output=github_failed_only --directory=.
38
+ ;;
39
+ *)
40
+ echo 'GitHub Actions and shell script linting is only supported on Linux and macOS.'
41
+ ;;
42
+ esac
@@ -12,8 +12,8 @@ command. Output format is auto-detected from the file extension (`.csv`,
12
12
 
13
13
  ## Requirements
14
14
 
15
- - Python 3.11+ on Windows with MetaTrader 5 installed (pdmt5 requires the
16
- MT5 terminal).
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
 
@@ -41,8 +41,10 @@ Global options MUST precede the subcommand.
41
41
 
42
42
  ### Parameter value formats
43
43
 
44
- - **Datetimes** (`--date-from`, `--date-to`): ISO 8601 (`2024-01-01` or
45
- `2024-01-01T12:00:00+00:00`). Naive values are treated as UTC.
44
+ - **Datetimes** (`--date-from`, `--date-to`): offset-free ISO 8601 values
45
+ (`2024-01-01` or `2024-01-01T12:00:00`). MT5 bounds are timezone-naive
46
+ trade-server wall-clock values; offset-bearing values are rejected and no
47
+ UTC-to-server-time conversion is inferred.
46
48
  - **Timeframe** (`--timeframe`): `M1`, `M2`, `M3`, `M4`, `M5`, `M6`, `M10`,
47
49
  `M12`, `M15`, `M20`, `M30`, `H1`, `H2`, `H3`, `H4`, `H6`, `H8`, `H12`,
48
50
  `D1`, `W1`, `MN1`, or the raw integer.
@@ -0,0 +1,2 @@
1
+ # Default owner
2
+ * @dceoy
@@ -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/gh-actions-for-devops/.github/workflows/python-package-lint-and-scan.yml@main # zizmor: ignore[unpinned-uses]
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/gh-actions-for-devops/.github/workflows/python-package-test.yml@main # zizmor: ignore[unpinned-uses]
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
- minimum-dependency-smoke-test:
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
- runs-on: windows-latest
61
- steps:
62
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6.0.2
63
- with:
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/gh-actions-for-devops/.github/workflows/python-package-mkdocs-gh-deploy.yml@main # zizmor: ignore[unpinned-uses]
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/gh-actions-for-devops/.github/workflows/github-codeql-analysis.yml@main # zizmor: ignore[unpinned-uses]
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
- uses: dceoy/gh-actions-for-devops/.github/workflows/dependabot-auto-merge.yml@main # zizmor: ignore[unpinned-uses]
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/gh-actions-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses]
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/gh-actions-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses]
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/gh-actions-for-devops/.github/workflows/python-package-release-on-pypi-and-github.yml@main # zizmor: ignore[unpinned-uses]
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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
42
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
43
43
  with:
44
44
  verbose: true
mt5cli-1.4.2/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.0
3
+ Version: 1.4.2
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.14,>=3.11
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
@@ -50,7 +50,7 @@ Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data han
50
50
  - **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history
51
51
  - **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values
52
52
  - **Connection management**: Optional credentials, server, and timeout configuration
53
- - **SQLite rate loading**: Load mt5cli-managed rate tables/views for offline workflows
53
+ - **SQLite rate loading**: Load canonical normalized `rates` storage for offline workflows
54
54
 
55
55
  ## Installation
56
56
 
@@ -69,7 +69,7 @@ pip install -U "mt5cli[parquet]" MetaTrader5
69
69
  Import `MT5Client` for generic MT5 data access, schema normalization, and optional order primitives.
70
70
 
71
71
  ```python
72
- from datetime import UTC, datetime
72
+ from datetime import datetime
73
73
  from pathlib import Path
74
74
 
75
75
  from mt5cli import (
@@ -103,8 +103,8 @@ export_dataframe(closed_rates, Path("rates.csv"), "csv")
103
103
  collect_history(
104
104
  Path("history.db"),
105
105
  symbols=["EURUSD"],
106
- date_from=datetime(2024, 1, 1, tzinfo=UTC),
107
- date_to=datetime(2024, 2, 1, tzinfo=UTC),
106
+ date_from=datetime(2024, 1, 1),
107
+ date_to=datetime(2024, 2, 1),
108
108
  datasets={Dataset.rates, Dataset.history_deals},
109
109
  )
110
110
 
@@ -113,9 +113,16 @@ update_history_with_config(
113
113
  output="history.db",
114
114
  symbols=["EURUSD"],
115
115
  config=build_config(login=12345),
116
+ date_to=datetime(2024, 2, 1),
116
117
  )
117
118
  ```
118
119
 
120
+ MT5 query bounds are timezone-naive trade-server wall-clock datetimes, as
121
+ required by pdmt5. Incremental history updates require an explicit `date_to`
122
+ because mt5cli cannot determine the current trade-server time. Offset-bearing
123
+ strings and timezone-aware `datetime` objects are rejected; mt5cli does not
124
+ infer or strip offsets to perform a conversion.
125
+
119
126
  Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Export and storage helpers are in `mt5cli.utils` (`Dataset`, `export_dataframe`) and `mt5cli.history`.
120
127
 
121
128
  `MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly.
@@ -335,6 +342,8 @@ OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telem
335
342
  For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:
336
343
 
337
344
  ```python
345
+ from datetime import datetime
346
+
338
347
  from pdmt5 import Mt5Config
339
348
  from mt5cli import update_history_with_config
340
349
  from mt5cli.utils import Dataset
@@ -347,19 +356,17 @@ update_history_with_config(
347
356
  datasets={Dataset.rates, Dataset.history_deals},
348
357
  timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
349
358
  lookback_hours=24,
350
- create_rate_views=True,
359
+ date_to=datetime(2024, 2, 1),
351
360
  with_views=True,
352
361
  include_account_events=True,
353
362
  )
354
363
  ```
355
364
 
356
365
  - **`collect-history`**: explicit date-range export into SQLite.
357
- - **`update_history`**: incremental append based on existing SQLite `MAX(time)` per symbol (and timeframe for rates); account-level deals use a separate cursor when `include_account_events=True`.
366
+ - **`update_history`**: incremental append based on existing SQLite `MAX(time)` per symbol (and timeframe for rates); account-level deals use a separate cursor when `include_account_events=True`. Pass an explicit naive trade-server `date_to`; mt5cli cannot determine the current server time. Rates are persisted only in the canonical normalized `rates` table.
358
367
  - **`rates` table**: normalized storage with `symbol` and `timeframe` columns.
359
- - **Rate compatibility views**: mt5cli manages all `rate_*` views. Naming is `rate_<symbol>__<timeframe>` when a symbol has one timeframe, otherwise `rate_<symbol>__<granularity>_<timeframe>` (for example `rate_EURUSD__M1_1`). Stale `rate_*` views are dropped and recreated when rates change for offline downstream tools.
360
- - **Rate view resolution**: use `resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases. Both accept `None` (or a missing path) and return deterministic default names unless `require_existing=True`.
361
- - **Rate view loading**: use `load_rate_data()` / `load_rate_data_from_connection()` to load a SQLite rate table or view into a `DatetimeIndex` DataFrame.
362
- - **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs, `resolve_rate_tables()` to map them to table/view names (pass `require_existing=True` for strict resolution), and `load_rate_series_from_sqlite()` to load them into a mapping keyed by `(symbol, integer timeframe)`. The loader requires existing managed views unless `explicit_tables` is supplied, and rejects duplicate `(symbol, timeframe)` targets.
368
+ - **Explicit table loading**: use `load_rate_data()` / `load_rate_data_from_connection()` for intentionally named custom SQLite rate tables.
369
+ - **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs and `load_rate_series_from_sqlite()` to load canonical `rates` rows into a mapping keyed by `(symbol, integer timeframe)`. Pass `explicit_tables` only for intentionally named custom tables; duplicate `(symbol, timeframe)` targets are rejected.
363
370
  - **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping. For long-running pollers, `collect_latest_rates_for_accounts_with_retries()` adds bounded exponential backoff that retries only recoverable MT5 errors and re-raises once `retry_count` is exhausted.
364
371
  - **Latest closed bars**: use `collect_latest_closed_rates_for_accounts()` when downstream logic must exclude the still-forming current bar. It fetches `count + 1` bars at `start_pos=0`, drops the last row with `drop_forming_rate_bar()`, and validates each series is non-empty. `collect_latest_closed_rates_by_granularity()` returns the same data keyed by `(symbol, granularity_name)` such as `("EURUSD", "M1")`.
365
372
 
@@ -376,7 +383,7 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
376
383
  ```
377
384
 
378
385
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code. For config dicts or nested structures loaded from YAML/TOML, use `substitute_mapping_values(data, keys={"login", "password"})` to expand placeholders only for caller-specified keys — key names are never hard-coded in mt5cli.
379
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
386
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols, date_to=...)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
380
387
  - **Trading helpers**: use `mt5_session()` for both market data and generic execution helpers. It owns initialization and shutdown for sessions it creates; caller-supplied clients remain caller-owned.
381
388
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
382
389
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
@@ -385,7 +392,6 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
385
392
 
386
393
  ## Requirements
387
394
 
388
- - Python 3.11+
389
395
  - Windows OS (MetaTrader 5 requirement)
390
396
  - MetaTrader 5 platform installed
391
397
 
@@ -435,7 +441,7 @@ updater = ThrottledHistoryUpdater(
435
441
  output="history.db", interval_seconds=60, suppress_errors=True
436
442
  )
437
443
  with mt5_session(Mt5Config(login=login)) as client:
438
- updater.update(client, ["EURUSD"])
444
+ updater.update(client, ["EURUSD"], date_to="2024-02-01")
439
445
  ```
440
446
 
441
447
  Read-only collectors can keep using `mt5_session()` and `MT5Client`.
@@ -21,7 +21,7 @@ Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data han
21
21
  - **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history
22
22
  - **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values
23
23
  - **Connection management**: Optional credentials, server, and timeout configuration
24
- - **SQLite rate loading**: Load mt5cli-managed rate tables/views for offline workflows
24
+ - **SQLite rate loading**: Load canonical normalized `rates` storage for offline workflows
25
25
 
26
26
  ## Installation
27
27
 
@@ -40,7 +40,7 @@ pip install -U "mt5cli[parquet]" MetaTrader5
40
40
  Import `MT5Client` for generic MT5 data access, schema normalization, and optional order primitives.
41
41
 
42
42
  ```python
43
- from datetime import UTC, datetime
43
+ from datetime import datetime
44
44
  from pathlib import Path
45
45
 
46
46
  from mt5cli import (
@@ -74,8 +74,8 @@ export_dataframe(closed_rates, Path("rates.csv"), "csv")
74
74
  collect_history(
75
75
  Path("history.db"),
76
76
  symbols=["EURUSD"],
77
- date_from=datetime(2024, 1, 1, tzinfo=UTC),
78
- date_to=datetime(2024, 2, 1, tzinfo=UTC),
77
+ date_from=datetime(2024, 1, 1),
78
+ date_to=datetime(2024, 2, 1),
79
79
  datasets={Dataset.rates, Dataset.history_deals},
80
80
  )
81
81
 
@@ -84,9 +84,16 @@ update_history_with_config(
84
84
  output="history.db",
85
85
  symbols=["EURUSD"],
86
86
  config=build_config(login=12345),
87
+ date_to=datetime(2024, 2, 1),
87
88
  )
88
89
  ```
89
90
 
91
+ MT5 query bounds are timezone-naive trade-server wall-clock datetimes, as
92
+ required by pdmt5. Incremental history updates require an explicit `date_to`
93
+ because mt5cli cannot determine the current trade-server time. Offset-bearing
94
+ strings and timezone-aware `datetime` objects are rejected; mt5cli does not
95
+ infer or strip offsets to perform a conversion.
96
+
90
97
  Schema contracts live in `mt5cli.schemas` (`DataKind`, `validate_schema`, `normalize_dataframe`). Export and storage helpers are in `mt5cli.utils` (`Dataset`, `export_dataframe`) and `mt5cli.history`.
91
98
 
92
99
  `MT5Client.order_send()` is a live execution primitive: it can place real trades on the connected account. mt5cli does not implement strategy logic, signal generation, backtesting, or optimization — downstream applications must gate live execution explicitly.
@@ -306,6 +313,8 @@ OpenTelemetry metrics are documented in [`docs/api/telemetry.md`](docs/api/telem
306
313
  For automated pipelines, use the importable incremental API instead of re-fetching fixed date ranges:
307
314
 
308
315
  ```python
316
+ from datetime import datetime
317
+
309
318
  from pdmt5 import Mt5Config
310
319
  from mt5cli import update_history_with_config
311
320
  from mt5cli.utils import Dataset
@@ -318,19 +327,17 @@ update_history_with_config(
318
327
  datasets={Dataset.rates, Dataset.history_deals},
319
328
  timeframes=["M1", "H1"], # default: all fixed MT5 timeframes
320
329
  lookback_hours=24,
321
- create_rate_views=True,
330
+ date_to=datetime(2024, 2, 1),
322
331
  with_views=True,
323
332
  include_account_events=True,
324
333
  )
325
334
  ```
326
335
 
327
336
  - **`collect-history`**: explicit date-range export into SQLite.
328
- - **`update_history`**: incremental append based on existing SQLite `MAX(time)` per symbol (and timeframe for rates); account-level deals use a separate cursor when `include_account_events=True`.
337
+ - **`update_history`**: incremental append based on existing SQLite `MAX(time)` per symbol (and timeframe for rates); account-level deals use a separate cursor when `include_account_events=True`. Pass an explicit naive trade-server `date_to`; mt5cli cannot determine the current server time. Rates are persisted only in the canonical normalized `rates` table.
329
338
  - **`rates` table**: normalized storage with `symbol` and `timeframe` columns.
330
- - **Rate compatibility views**: mt5cli manages all `rate_*` views. Naming is `rate_<symbol>__<timeframe>` when a symbol has one timeframe, otherwise `rate_<symbol>__<granularity>_<timeframe>` (for example `rate_EURUSD__M1_1`). Stale `rate_*` views are dropped and recreated when rates change for offline downstream tools.
331
- - **Rate view resolution**: use `resolve_rate_view_name()` / `resolve_rate_view_names()` to map symbols and granularities to existing SQLite compatibility views without creating databases. Both accept `None` (or a missing path) and return deterministic default names unless `require_existing=True`.
332
- - **Rate view loading**: use `load_rate_data()` / `load_rate_data_from_connection()` to load a SQLite rate table or view into a `DatetimeIndex` DataFrame.
333
- - **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs, `resolve_rate_tables()` to map them to table/view names (pass `require_existing=True` for strict resolution), and `load_rate_series_from_sqlite()` to load them into a mapping keyed by `(symbol, integer timeframe)`. The loader requires existing managed views unless `explicit_tables` is supplied, and rejects duplicate `(symbol, timeframe)` targets.
339
+ - **Explicit table loading**: use `load_rate_data()` / `load_rate_data_from_connection()` for intentionally named custom SQLite rate tables.
340
+ - **Multi-series rate loading**: use `build_rate_targets()` to build neutral `RateTarget(symbol, timeframe)` pairs and `load_rate_series_from_sqlite()` to load canonical `rates` rows into a mapping keyed by `(symbol, integer timeframe)`. Pass `explicit_tables` only for intentionally named custom tables; duplicate `(symbol, timeframe)` targets are rejected.
334
341
  - **Multi-account latest rates**: use `collect_latest_rates_for_accounts()` with `AccountSpec` to read the latest bars for several account groups, merged into a `(symbol, integer timeframe)` mapping. For long-running pollers, `collect_latest_rates_for_accounts_with_retries()` adds bounded exponential backoff that retries only recoverable MT5 errors and re-raises once `retry_count` is exhausted.
335
342
  - **Latest closed bars**: use `collect_latest_closed_rates_for_accounts()` when downstream logic must exclude the still-forming current bar. It fetches `count + 1` bars at `start_pos=0`, drops the last row with `drop_forming_rate_bar()`, and validates each series is non-empty. `collect_latest_closed_rates_by_granularity()` returns the same data keyed by `(symbol, granularity_name)` such as `("EURUSD", "M1")`.
336
343
 
@@ -347,7 +354,7 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
347
354
  ```
348
355
 
349
356
  - **Credential resolution**: use `resolve_account_spec()` / `resolve_account_specs()` to merge explicit override values over `AccountSpec` fields and expand `${ENV_VAR}` placeholders (via `substitute_env_placeholders()`), raising `ValueError` for missing variables. This keeps secrets out of plan/config files without coupling to any strategy code. For config dicts or nested structures loaded from YAML/TOML, use `substitute_mapping_values(data, keys={"login", "password"})` to expand placeholders only for caller-specified keys — key names are never hard-coded in mt5cli.
350
- - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
357
+ - **Throttled history updates**: use `ThrottledHistoryUpdater` to wrap `update_history()` with a minimum `interval_seconds` between successful runs (monotonic clock). Call `should_update()` / `update(client, symbols, date_to=...)` from an application loop; errors propagate by default, or pass `suppress_errors=True` to swallow recoverable `Mt5*Error`, `sqlite3.Error`, `ValueError`, and `OSError` without advancing the throttle (`AttributeError` / `TypeError` always propagate, since the client passed to `update()` must implement the canonical `HistoryClient` method names). Pass `update_backend` to inject a custom history update callable (same keyword arguments as `update_history`) instead of monkey-patching `mt5cli.history.update_history`.
351
358
  - **Trading helpers**: use `mt5_session()` for both market data and generic execution helpers. It owns initialization and shutdown for sessions it creates; caller-supplied clients remain caller-owned.
352
359
  - **Granularity-keyed rate loading**: `load_rate_series_by_granularity()` builds targets with `build_rate_targets()`, loads them with `load_rate_series_from_sqlite()`, and returns a mapping keyed by `(symbol | None, granularity_name)` such as `("EURUSD", "M1")` to reduce downstream boilerplate.
353
360
  - **MT5 session helper**: use the `mt5_session()` context manager to attach to (or, when `Mt5Config.path` is set, launch) an MT5 terminal, log in, and yield a connected `MT5Client` that shuts down on exit.
@@ -356,7 +363,6 @@ eurusd_m1 = rates["EURUSD", "M1"] # closed bars only
356
363
 
357
364
  ## Requirements
358
365
 
359
- - Python 3.11+
360
366
  - Windows OS (MetaTrader 5 requirement)
361
367
  - MetaTrader 5 platform installed
362
368
 
@@ -406,7 +412,7 @@ updater = ThrottledHistoryUpdater(
406
412
  output="history.db", interval_seconds=60, suppress_errors=True
407
413
  )
408
414
  with mt5_session(Mt5Config(login=login)) as client:
409
- updater.update(client, ["EURUSD"])
415
+ updater.update(client, ["EURUSD"], date_to="2024-02-01")
410
416
  ```
411
417
 
412
418
  Read-only collectors can keep using `mt5_session()` and `MT5Client`.