mt5cli 1.3.1__tar.gz → 1.3.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.
Files changed (73) hide show
  1. {mt5cli-1.3.1 → mt5cli-1.3.3}/PKG-INFO +1 -1
  2. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/public-contract.md +1 -1
  3. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/trading.md +64 -42
  4. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/trading.py +360 -251
  5. {mt5cli-1.3.1 → mt5cli-1.3.3}/pyproject.toml +1 -1
  6. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_trading.py +802 -648
  7. {mt5cli-1.3.1 → mt5cli-1.3.3}/uv.lock +1 -1
  8. {mt5cli-1.3.1 → mt5cli-1.3.3}/.agents/skills/local-qa/SKILL.md +0 -0
  9. {mt5cli-1.3.1 → mt5cli-1.3.3}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  10. {mt5cli-1.3.1 → mt5cli-1.3.3}/.agents/skills/mt5cli/SKILL.md +0 -0
  11. {mt5cli-1.3.1 → mt5cli-1.3.3}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  12. {mt5cli-1.3.1 → mt5cli-1.3.3}/.claude/agents/codex.md +0 -0
  13. {mt5cli-1.3.1 → mt5cli-1.3.3}/.claude/settings.json +0 -0
  14. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/FUNDING.yml +0 -0
  15. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/dependabot.yml +0 -0
  16. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/renovate.json +0 -0
  17. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/workflows/ci.yml +0 -0
  18. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/workflows/claude.yml +0 -0
  19. {mt5cli-1.3.1 → mt5cli-1.3.3}/.github/workflows/release.yml +0 -0
  20. {mt5cli-1.3.1 → mt5cli-1.3.3}/.gitignore +0 -0
  21. {mt5cli-1.3.1 → mt5cli-1.3.3}/AGENTS.md +0 -0
  22. {mt5cli-1.3.1 → mt5cli-1.3.3}/CLAUDE.md +0 -0
  23. {mt5cli-1.3.1 → mt5cli-1.3.3}/LICENSE +0 -0
  24. {mt5cli-1.3.1 → mt5cli-1.3.3}/README.md +0 -0
  25. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/cli.md +0 -0
  26. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/client.md +0 -0
  27. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/converters.md +0 -0
  28. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/exceptions.md +0 -0
  29. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/grafana.md +0 -0
  30. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/history.md +0 -0
  31. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/index.md +0 -0
  32. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/marketdata.md +0 -0
  33. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/observability.md +0 -0
  34. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/schemas.md +0 -0
  35. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/telemetry.md +0 -0
  36. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/api/utils.md +0 -0
  37. {mt5cli-1.3.1 → mt5cli-1.3.3}/docs/index.md +0 -0
  38. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/README.md +0 -0
  39. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/compose.yml +0 -0
  40. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  41. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  42. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  43. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  44. {mt5cli-1.3.1 → mt5cli-1.3.3}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  45. {mt5cli-1.3.1 → mt5cli-1.3.3}/mkdocs.yml +0 -0
  46. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/__init__.py +0 -0
  47. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/__main__.py +0 -0
  48. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/cli.py +0 -0
  49. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/client.py +0 -0
  50. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/contract.py +0 -0
  51. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/converters.py +0 -0
  52. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/exceptions.py +0 -0
  53. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/grafana.py +0 -0
  54. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/history.py +0 -0
  55. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/marketdata.py +0 -0
  56. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/observability.py +0 -0
  57. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/retry.py +0 -0
  58. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/schemas.py +0 -0
  59. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/telemetry.py +0 -0
  60. {mt5cli-1.3.1 → mt5cli-1.3.3}/mt5cli/utils.py +0 -0
  61. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/__init__.py +0 -0
  62. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/conftest.py +0 -0
  63. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_cli.py +0 -0
  64. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_client.py +0 -0
  65. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_contracts.py +0 -0
  66. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_examples.py +0 -0
  67. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_grafana.py +0 -0
  68. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_history.py +0 -0
  69. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_marketdata.py +0 -0
  70. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_observability.py +0 -0
  71. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_packaging.py +0 -0
  72. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_telemetry.py +0 -0
  73. {mt5cli-1.3.1 → mt5cli-1.3.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.3.1
3
+ Version: 1.3.3
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>
@@ -102,7 +102,7 @@ strategy entries, exits, Kelly sizing, or signal logic.
102
102
  | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
103
103
  | `get_account_snapshot`, `get_symbol_snapshot`, `get_tick_snapshot`, `get_positions_frame` | Normalized account/symbol/tick/position views |
104
104
  | `extract_tick_price` | Positive finite bid/ask extraction from tick mappings |
105
- | `TickClockNormalizer` | Connection-scoped, copied-tick-validated UTC tick normalization |
105
+ | `TickClockNormalizer` | Connection-scoped, host-clock-validated UTC tick normalization |
106
106
  | `TickClockCalibration` | Server-clock calibration diagnostics (offset, evidence, status) |
107
107
  | `NormalizedTickSnapshot` | Typed latest-tick contract with raw vs validated UTC timestamps |
108
108
  | `CalibrationStatus`, `ClockStatus` | Literal status types for clock calibration and normalization |
@@ -204,16 +204,27 @@ provide entry-deal classification, Kelly sizing, or any betting-specific helpers
204
204
 
205
205
  ## Timestamp sources and broker clock offsets
206
206
 
207
- The MT5 Python documentation describes tick and bar data copied by the
208
- `copy_ticks_*` and `copy_rates_*` functions as UTC. It does not document a
209
- different timezone contract for `symbol_info_tick()`, nor should deal epochs
210
- be assumed to use a broker wall clock without source-specific evidence.
207
+ MetaQuotes documents `copy_ticks_range()` and `copy_rates_range()` results,
208
+ and the date bounds they accept, as UTC, and does not document a different
209
+ timezone contract for `symbol_info_tick()`. In production on at least one
210
+ OANDA-style broker/terminal, however, `symbol_info_tick()` returned epochs
211
+ carrying the broker's own wall-clock label (UTC+3), not true UTC: treating
212
+ `copy_ticks_range()` as an independent UTC reference for the live tick clock
213
+ was the direct cause of a production calibration failure (see
214
+ [dceoy/mteor#428](https://github.com/dceoy/mteor/issues/428)), because a
215
+ query window built from the host clock missed the live event entirely, off
216
+ by exactly the broker's offset. mt5cli does not call `copy_ticks_range()` or
217
+ `copy_rates_range()` to calibrate the live tick clock, and does not
218
+ independently verify their documented UTC contract on any broker or
219
+ terminal — treat copied-history timestamps as broker/terminal-dependent
220
+ absent evidence from your own broker/terminal combination.
211
221
 
212
222
  `get_tick_snapshot()` preserves the numeric MT5 epoch value in `time`; it does
213
- not expose pdmt5's timezone-naive `Timestamp` conversion or alter the instant.
214
- Any broker-specific offset handling therefore requires evidence from the live
215
- data source in use and must be applied separately. Do not subtract a presumed
216
- UTC+2 or UTC+3 offset from timestamps that are already UTC.
223
+ not expose pdmt5's timezone-naive `Timestamp` conversion or alter the instant,
224
+ and it never applies any offset correction. Any broker-specific offset
225
+ handling requires independent evidence — see `TickClockNormalizer` below —
226
+ and must be applied separately. Do not subtract a presumed UTC+2 or UTC+3
227
+ offset from a raw timestamp without that evidence.
217
228
 
218
229
  No offset is applied automatically by history retrieval helpers.
219
230
 
@@ -251,46 +262,54 @@ A normalized snapshot keeps the raw/UTC distinction explicit:
251
262
 
252
263
  ### Calibration evidence
253
264
 
254
- The offset is never inferred from `latest_tick_time - now` alone. Each
255
- calibration sample fetches the live `symbol_info_tick()` value **and** recent
256
- UTC-labeled `copy_ticks_range()` data, then searches every recent copied row
257
- (not just the newest) for one that matches the live tick on its shared
258
- `bid`/`ask`/`last`/`volume` fields (with at least one agreeing positive price
259
- field); the newest matching row sets the resulting offset, which must align
260
- to a 30-minute increment within the realistic UTC-14..UTC+14 range.
261
- `time_msc` is preferred over second-resolution `time` on both sides when
262
- available.
265
+ `copy_ticks_range()` is not used for calibration: its documented UTC contract
266
+ is broker/terminal-dependent and not independently verified here (see
267
+ above), so it cannot serve as an out-of-band UTC reference. Instead, the only
268
+ independent reference available is **this process's own clock**: every
269
+ calibration poll
270
+ fetches one live `symbol_info_tick()` value and stamps it with
271
+ `datetime.now(UTC)` at the moment it is received. Comparing that host receipt
272
+ time against the tick's own server-labeled epoch yields one candidate offset,
273
+ which must align to a 30-minute increment within the realistic UTC-14..UTC+14
274
+ range. `time_msc` is preferred over second-resolution `time` when available.
275
+
276
+ A single poll is never trusted on its own. A closed, stale, or illiquid
277
+ symbol keeps returning the exact same last-traded tick, so its epoch never
278
+ changes between polls — that ordinary tick age is not calibration evidence.
279
+ Only a poll whose epoch _differs_ from the previous poll (comparison is
280
+ inequality, not "must increase": a genuine broker offset **decrease**, e.g. a
281
+ UTC+3 to UTC+2 DST fallback, relabels the next fresh event with a numerically
282
+ _smaller_ epoch than the last one observed under the old, larger offset)
283
+ proves the broker produced a new event, and only then is its offset computed.
263
284
 
264
285
  An offset is accepted only after `min_agreeing_samples` (default 2)
265
- **distinct** matched tick events agree on the same rounded offset — repeated
266
- observations of one unchanged tick never count twice, so an illiquid symbol
286
+ **distinct** fresh tick events agree on the same rounded offset — repeated
287
+ polls of one unchanged tick never count as evidence, so an illiquid symbol
267
288
  cannot self-confirm. Evidence can come from multiple samples of one actively
268
289
  updating symbol (paced by `sample_interval_seconds`) or across several
269
- symbols passed to the constructor. Any two matched samples that disagree
270
- abort the calibration (`offset_disagreement`).
271
-
272
- The `copy_ticks_range()` query backing each sample extends its upper bound
273
- `_MAX_FUTURE_SKEW_SECONDS` (2 minutes) past the host clock's current time, in
274
- addition to covering the full trailing `copied_window_seconds`. A live tick's
275
- true UTC event is not always at or before the host clock — feed/broker clock
276
- skew can put it a few seconds ahead — and without that tolerance the matching
277
- copied row would never be queried, so calibration would always report
278
- `no_matching_event`. The tolerance is bounded, not open-ended: an event
279
- further ahead than `_MAX_FUTURE_SKEW_SECONDS` is still excluded from the
280
- query and cannot be matched.
290
+ symbols passed to the constructor; a closed symbol among several configured
291
+ symbols simply contributes no evidence rather than blocking the others. Any
292
+ two fresh events that disagree on the rounded offset abort the calibration
293
+ (`offset_disagreement`) instead of being averaged or resolved by recency.
281
294
 
282
295
  ### Failure modes and fail-closed behavior
283
296
 
284
- When calibration cannot be established safely — closed market or weekend
285
- (`no_copied_ticks`), price disagreement or a non-half-hour delta
286
- (`no_matching_event`), a single matched event only (`insufficient_agreement`),
287
- missing live data (`no_live_tick`), or an unrealistic delta
288
- (`implausible_offset`) — snapshots fail closed: `clock_status` is
297
+ When calibration cannot be established safely — no symbol ever produced a
298
+ fresh (changed-epoch) tick, e.g. every configured symbol is closed or
299
+ illiquid (`not_advancing`), a fresh event's raw offset does not round cleanly
300
+ to a plausible bucket (`unstable_offset`), an unrealistic rounded delta
301
+ (`implausible_offset`), too few distinct agreeing events
302
+ (`insufficient_agreement`), disagreeing events (`offset_disagreement`), or
303
+ missing live data (`no_live_tick`) — snapshots fail closed: `clock_status` is
289
304
  `"uncalibrated"`, `time_utc` and `server_clock_offset_seconds` are `None`,
290
305
  and the raw fields remain available. A raw timestamp is never silently
291
306
  treated as UTC. `normalizer.calibration` exposes the full
292
307
  `TickClockCalibration` diagnostics: status, offset, distinct sample count,
293
- evidence symbols, and last calibration time (`to_dict()` for serialization).
308
+ evidence symbols, last calibration time, and (for troubleshooting a failed
309
+ attempt) the most recently considered sample's symbol, raw server-labeled
310
+ time, host observation time, and raw (pre-rounding) offset —
311
+ `to_dict()` for serialization, and all of it is included in the one
312
+ concise warning logged per failed calibration attempt.
294
313
 
295
314
  ### Caching and revalidation
296
315
 
@@ -304,11 +323,14 @@ symbols and calls. A cached offset is recomputed in three cases:
304
323
  future — evidence that the broker offset grew, e.g. a UTC+2 to UTC+3
305
324
  transition.
306
325
  - Periodically, at most once per `revalidation_interval_seconds` (default 5
307
- minutes), a single confirming sample is taken against the still-cached
308
- offset; a disagreement forces full recalibration. This is what catches a
309
- broker offset _decrease_ (e.g. UTC+3 to UTC+2): the resulting normalized
310
- time looks stale rather than future, which a future-skew check alone
311
- cannot distinguish from ordinary quiet-market staleness.
326
+ minutes), each configured symbol is polled once and compared against that
327
+ symbol's last known observation (from the initial calibration or a prior
328
+ revalidation) until one confirms the still-cached offset. Closed or
329
+ inconclusive symbols are skipped; a detected disagreement forces full
330
+ recalibration. This is what catches a broker offset _decrease_ (e.g. UTC+3
331
+ to UTC+2): the resulting normalized time looks stale rather than future,
332
+ which a future-skew check alone cannot distinguish from ordinary
333
+ quiet-market staleness.
312
334
 
313
335
  If recalibration still cannot validate the timestamp, the snapshot fails
314
336
  closed. Failed calibrations are recorded for diagnostics but never reused;