mt5cli 1.3.2__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.2 → mt5cli-1.3.3}/PKG-INFO +1 -1
  2. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/public-contract.md +1 -1
  3. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/trading.md +64 -44
  4. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/trading.py +352 -254
  5. {mt5cli-1.3.2 → mt5cli-1.3.3}/pyproject.toml +1 -1
  6. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_trading.py +761 -738
  7. {mt5cli-1.3.2 → mt5cli-1.3.3}/uv.lock +1 -1
  8. {mt5cli-1.3.2 → mt5cli-1.3.3}/.agents/skills/local-qa/SKILL.md +0 -0
  9. {mt5cli-1.3.2 → mt5cli-1.3.3}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  10. {mt5cli-1.3.2 → mt5cli-1.3.3}/.agents/skills/mt5cli/SKILL.md +0 -0
  11. {mt5cli-1.3.2 → mt5cli-1.3.3}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  12. {mt5cli-1.3.2 → mt5cli-1.3.3}/.claude/agents/codex.md +0 -0
  13. {mt5cli-1.3.2 → mt5cli-1.3.3}/.claude/settings.json +0 -0
  14. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/FUNDING.yml +0 -0
  15. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/dependabot.yml +0 -0
  16. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/renovate.json +0 -0
  17. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/workflows/ci.yml +0 -0
  18. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/workflows/claude.yml +0 -0
  19. {mt5cli-1.3.2 → mt5cli-1.3.3}/.github/workflows/release.yml +0 -0
  20. {mt5cli-1.3.2 → mt5cli-1.3.3}/.gitignore +0 -0
  21. {mt5cli-1.3.2 → mt5cli-1.3.3}/AGENTS.md +0 -0
  22. {mt5cli-1.3.2 → mt5cli-1.3.3}/CLAUDE.md +0 -0
  23. {mt5cli-1.3.2 → mt5cli-1.3.3}/LICENSE +0 -0
  24. {mt5cli-1.3.2 → mt5cli-1.3.3}/README.md +0 -0
  25. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/cli.md +0 -0
  26. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/client.md +0 -0
  27. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/converters.md +0 -0
  28. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/exceptions.md +0 -0
  29. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/grafana.md +0 -0
  30. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/history.md +0 -0
  31. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/index.md +0 -0
  32. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/marketdata.md +0 -0
  33. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/observability.md +0 -0
  34. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/schemas.md +0 -0
  35. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/telemetry.md +0 -0
  36. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/api/utils.md +0 -0
  37. {mt5cli-1.3.2 → mt5cli-1.3.3}/docs/index.md +0 -0
  38. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/README.md +0 -0
  39. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/compose.yml +0 -0
  40. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  41. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  42. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  43. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  44. {mt5cli-1.3.2 → mt5cli-1.3.3}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  45. {mt5cli-1.3.2 → mt5cli-1.3.3}/mkdocs.yml +0 -0
  46. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/__init__.py +0 -0
  47. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/__main__.py +0 -0
  48. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/cli.py +0 -0
  49. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/client.py +0 -0
  50. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/contract.py +0 -0
  51. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/converters.py +0 -0
  52. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/exceptions.py +0 -0
  53. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/grafana.py +0 -0
  54. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/history.py +0 -0
  55. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/marketdata.py +0 -0
  56. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/observability.py +0 -0
  57. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/retry.py +0 -0
  58. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/schemas.py +0 -0
  59. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/telemetry.py +0 -0
  60. {mt5cli-1.3.2 → mt5cli-1.3.3}/mt5cli/utils.py +0 -0
  61. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/__init__.py +0 -0
  62. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/conftest.py +0 -0
  63. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_cli.py +0 -0
  64. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_client.py +0 -0
  65. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_contracts.py +0 -0
  66. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_examples.py +0 -0
  67. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_grafana.py +0 -0
  68. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_history.py +0 -0
  69. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_marketdata.py +0 -0
  70. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_observability.py +0 -0
  71. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_packaging.py +0 -0
  72. {mt5cli-1.3.2 → mt5cli-1.3.3}/tests/test_telemetry.py +0 -0
  73. {mt5cli-1.3.2 → 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.2
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,47 +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
- positive `bid`/`ask`/`last` fields. Volume is not used as event identity
259
- because latest-tick and copied-tick views can represent OTC quote volume
260
- differently. The newest matching row sets the resulting offset, which must
261
- align to a 30-minute increment within the realistic UTC-14..UTC+14 range.
262
- `time_msc` is preferred over second-resolution `time` on both sides when
263
- 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.
264
284
 
265
285
  An offset is accepted only after `min_agreeing_samples` (default 2)
266
- **distinct** matched tick events agree on the same rounded offset — repeated
267
- 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
268
288
  cannot self-confirm. Evidence can come from multiple samples of one actively
269
289
  updating symbol (paced by `sample_interval_seconds`) or across several
270
- symbols passed to the constructor. Any two matched samples that disagree
271
- abort the calibration (`offset_disagreement`).
272
-
273
- The `copy_ticks_range()` query backing each sample extends its upper bound
274
- `_MAX_FUTURE_SKEW_SECONDS` (2 minutes) past the host clock's current time, in
275
- addition to covering the full trailing `copied_window_seconds`. A live tick's
276
- true UTC event is not always at or before the host clock — feed/broker clock
277
- skew can put it a few seconds ahead — and without that tolerance the matching
278
- copied row would never be queried, so calibration would always report
279
- `no_matching_event`. The tolerance is bounded, not open-ended: an event
280
- further ahead than `_MAX_FUTURE_SKEW_SECONDS` is still excluded from the
281
- 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.
282
294
 
283
295
  ### Failure modes and fail-closed behavior
284
296
 
285
- When calibration cannot be established safely — closed market or weekend
286
- (`no_copied_ticks`), price disagreement or a non-half-hour delta
287
- (`no_matching_event`), a single matched event only (`insufficient_agreement`),
288
- missing live data (`no_live_tick`), or an unrealistic delta
289
- (`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
290
304
  `"uncalibrated"`, `time_utc` and `server_clock_offset_seconds` are `None`,
291
305
  and the raw fields remain available. A raw timestamp is never silently
292
306
  treated as UTC. `normalizer.calibration` exposes the full
293
307
  `TickClockCalibration` diagnostics: status, offset, distinct sample count,
294
- 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.
295
313
 
296
314
  ### Caching and revalidation
297
315
 
@@ -305,12 +323,14 @@ symbols and calls. A cached offset is recomputed in three cases:
305
323
  future — evidence that the broker offset grew, e.g. a UTC+2 to UTC+3
306
324
  transition.
307
325
  - Periodically, at most once per `revalidation_interval_seconds` (default 5
308
- minutes), configured symbols are sampled until one confirms the
309
- still-cached offset. Closed or inconclusive symbols are skipped; a detected
310
- disagreement forces full recalibration. This is what catches a broker
311
- offset _decrease_ (e.g. UTC+3 to UTC+2): the resulting normalized time
312
- looks stale rather than future, which a future-skew check alone cannot
313
- 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.
314
334
 
315
335
  If recalibration still cannot validate the timestamp, the snapshot fails
316
336
  closed. Failed calibrations are recorded for diagnostics but never reused;