mt5cli 1.3.0__tar.gz → 1.3.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.3.0 → mt5cli-1.3.1}/PKG-INFO +1 -1
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/trading.md +10 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/trading.py +14 -3
- {mt5cli-1.3.0 → mt5cli-1.3.1}/pyproject.toml +3 -1
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_trading.py +107 -2
- {mt5cli-1.3.0 → mt5cli-1.3.1}/uv.lock +8 -1
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.claude/agents/codex.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.claude/settings.json +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/FUNDING.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/dependabot.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/renovate.json +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/workflows/ci.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/workflows/claude.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.github/workflows/release.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/.gitignore +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/AGENTS.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/CLAUDE.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/LICENSE +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/README.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/cli.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/client.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/converters.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/exceptions.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/grafana.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/history.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/index.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/marketdata.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/observability.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/public-contract.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/schemas.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/telemetry.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/api/utils.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/docs/index.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/README.md +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/compose.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-market.json +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mkdocs.yml +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/__init__.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/__main__.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/cli.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/client.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/contract.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/converters.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/exceptions.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/grafana.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/history.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/marketdata.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/observability.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/retry.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/schemas.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/telemetry.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/mt5cli/utils.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/__init__.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/conftest.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_cli.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_client.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_contracts.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_examples.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_grafana.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_history.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_marketdata.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_observability.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_packaging.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_telemetry.py +0 -0
- {mt5cli-1.3.0 → mt5cli-1.3.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5cli
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.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>
|
|
@@ -269,6 +269,16 @@ updating symbol (paced by `sample_interval_seconds`) or across several
|
|
|
269
269
|
symbols passed to the constructor. Any two matched samples that disagree
|
|
270
270
|
abort the calibration (`offset_disagreement`).
|
|
271
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.
|
|
281
|
+
|
|
272
282
|
### Failure modes and fail-closed behavior
|
|
273
283
|
|
|
274
284
|
When calibration cannot be established safely — closed market or weekend
|
|
@@ -1060,7 +1060,14 @@ def _recent_copied_ticks(
|
|
|
1060
1060
|
window_seconds: float,
|
|
1061
1061
|
now_epoch: float,
|
|
1062
1062
|
) -> pd.DataFrame | None:
|
|
1063
|
-
"""Fetch copied ticks for the trailing UTC window ending now.
|
|
1063
|
+
"""Fetch copied ticks for the trailing UTC window ending just past now.
|
|
1064
|
+
|
|
1065
|
+
The upper query bound extends ``_MAX_FUTURE_SKEW_SECONDS`` past
|
|
1066
|
+
``now_epoch`` so a live tick whose true UTC event is slightly ahead of
|
|
1067
|
+
the host clock (feed/server clock skew, not a broker wall-clock label)
|
|
1068
|
+
still falls inside the queried range and can be matched; that tolerance
|
|
1069
|
+
matches the future-skew bound already enforced on normalized snapshots,
|
|
1070
|
+
so it cannot admit an event further ahead than the supported policy.
|
|
1064
1071
|
|
|
1065
1072
|
Returns:
|
|
1066
1073
|
The copied-tick DataFrame, or ``None`` when retrieval fails or the
|
|
@@ -1069,13 +1076,14 @@ def _recent_copied_ticks(
|
|
|
1069
1076
|
flags = getattr(getattr(client, "mt5", None), "COPY_TICKS_ALL", None)
|
|
1070
1077
|
if not isinstance(flags, int):
|
|
1071
1078
|
flags = _COPY_TICKS_ALL_FALLBACK
|
|
1079
|
+
query_end_epoch = now_epoch + _MAX_FUTURE_SKEW_SECONDS
|
|
1072
1080
|
try:
|
|
1073
1081
|
frame = cast(
|
|
1074
1082
|
"object",
|
|
1075
1083
|
client.copy_ticks_range(
|
|
1076
1084
|
symbol,
|
|
1077
1085
|
datetime.fromtimestamp(now_epoch - window_seconds, tz=UTC),
|
|
1078
|
-
datetime.fromtimestamp(
|
|
1086
|
+
datetime.fromtimestamp(query_end_epoch, tz=UTC),
|
|
1079
1087
|
flags,
|
|
1080
1088
|
),
|
|
1081
1089
|
)
|
|
@@ -1242,7 +1250,10 @@ class TickClockNormalizer:
|
|
|
1242
1250
|
sample_interval_seconds: Pause between consecutive samples so an
|
|
1243
1251
|
actively updating symbol can produce distinct tick events.
|
|
1244
1252
|
copied_window_seconds: Trailing UTC window queried from
|
|
1245
|
-
``copy_ticks_range`` per sample
|
|
1253
|
+
``copy_ticks_range`` per sample, ending
|
|
1254
|
+
``_MAX_FUTURE_SKEW_SECONDS`` past the host clock's current
|
|
1255
|
+
time so a live tick slightly ahead of the host clock (feed
|
|
1256
|
+
clock skew) remains matchable.
|
|
1246
1257
|
max_calibration_age_seconds: Age after which a cached calibration
|
|
1247
1258
|
is recomputed unconditionally (bounds DST-transition
|
|
1248
1259
|
staleness).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mt5cli"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.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"}]
|
|
@@ -40,6 +40,8 @@ Repository = "https://github.com/dceoy/mt5cli.git"
|
|
|
40
40
|
|
|
41
41
|
[tool.uv]
|
|
42
42
|
required-environments = ["platform_system == 'Windows'"]
|
|
43
|
+
exclude-newer = "1 week"
|
|
44
|
+
exclude-newer-package = { pdmt5 = false }
|
|
43
45
|
|
|
44
46
|
[dependency-groups]
|
|
45
47
|
dev = [
|
|
@@ -14,12 +14,15 @@ from numpy import int64 as np_int64
|
|
|
14
14
|
from pdmt5 import Mt5RuntimeError
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
|
|
17
19
|
from pytest_mock import MockerFixture
|
|
18
20
|
|
|
19
21
|
from mt5cli import trading
|
|
20
22
|
from mt5cli.client import MT5Client
|
|
21
23
|
from mt5cli.exceptions import Mt5OperationError
|
|
22
24
|
from mt5cli.trading import (
|
|
25
|
+
_MAX_FUTURE_SKEW_SECONDS, # type: ignore[reportPrivateUsage]
|
|
23
26
|
MarginVolume,
|
|
24
27
|
OrderExecutionResult,
|
|
25
28
|
OrderLimits,
|
|
@@ -4145,6 +4148,31 @@ def _clock_client(
|
|
|
4145
4148
|
return client
|
|
4146
4149
|
|
|
4147
4150
|
|
|
4151
|
+
def _windowed_copy_ticks_range(
|
|
4152
|
+
frame: pd.DataFrame,
|
|
4153
|
+
) -> Callable[[str, datetime, datetime, int], pd.DataFrame]:
|
|
4154
|
+
"""Build a ``copy_ticks_range`` fake that filters rows by query bounds.
|
|
4155
|
+
|
|
4156
|
+
Real MT5 only returns rows within ``[date_from, date_to]``; a plain
|
|
4157
|
+
canned return value would hide a regression where the production code
|
|
4158
|
+
queries too narrow a window, since the mock would return the same rows
|
|
4159
|
+
regardless of the arguments it was called with.
|
|
4160
|
+
"""
|
|
4161
|
+
|
|
4162
|
+
def _copy_ticks_range(
|
|
4163
|
+
_symbol: str,
|
|
4164
|
+
date_from: datetime,
|
|
4165
|
+
date_to: datetime,
|
|
4166
|
+
_flags: int,
|
|
4167
|
+
) -> pd.DataFrame:
|
|
4168
|
+
from_epoch = date_from.timestamp()
|
|
4169
|
+
to_epoch = date_to.timestamp()
|
|
4170
|
+
mask = (frame["time"] >= from_epoch) & (frame["time"] <= to_epoch)
|
|
4171
|
+
return frame[mask].reset_index(drop=True)
|
|
4172
|
+
|
|
4173
|
+
return _copy_ticks_range
|
|
4174
|
+
|
|
4175
|
+
|
|
4148
4176
|
class TestTickClockNormalizer:
|
|
4149
4177
|
"""Tests for TickClockNormalizer calibration and UTC normalization."""
|
|
4150
4178
|
|
|
@@ -4192,7 +4220,7 @@ class TestTickClockNormalizer:
|
|
|
4192
4220
|
mt5_flag: int | None,
|
|
4193
4221
|
expected_flag: int,
|
|
4194
4222
|
) -> None:
|
|
4195
|
-
"""Copied ticks are queried over a trailing
|
|
4223
|
+
"""Copied ticks are queried over a window trailing and past now."""
|
|
4196
4224
|
_freeze_clock(mocker)
|
|
4197
4225
|
event = _CLOCK_NOW_EPOCH - 1
|
|
4198
4226
|
client = _clock_client(
|
|
@@ -4213,10 +4241,87 @@ class TestTickClockNormalizer:
|
|
|
4213
4241
|
client.copy_ticks_range.assert_called_once_with(
|
|
4214
4242
|
"SING30",
|
|
4215
4243
|
datetime.fromtimestamp(_CLOCK_NOW_EPOCH - 300.0, tz=UTC),
|
|
4216
|
-
datetime.fromtimestamp(
|
|
4244
|
+
datetime.fromtimestamp(
|
|
4245
|
+
_CLOCK_NOW_EPOCH + _MAX_FUTURE_SKEW_SECONDS,
|
|
4246
|
+
tz=UTC,
|
|
4247
|
+
),
|
|
4217
4248
|
expected_flag,
|
|
4218
4249
|
)
|
|
4219
4250
|
|
|
4251
|
+
def test_live_tick_ahead_of_host_clock_matches_within_widened_window(
|
|
4252
|
+
self,
|
|
4253
|
+
mocker: MockerFixture,
|
|
4254
|
+
) -> None:
|
|
4255
|
+
"""A live tick ~16s ahead of the host clock still calibrates.
|
|
4256
|
+
|
|
4257
|
+
Reproduces the dceoy/mteor#428 production failure: the feed/server
|
|
4258
|
+
clock can run slightly ahead of the host OS clock, so the true UTC
|
|
4259
|
+
event backing a live tick can itself land a few seconds past
|
|
4260
|
+
``datetime.now(UTC)``. Querying ``copy_ticks_range`` with
|
|
4261
|
+
``date_to=now`` would exclude that event, leaving only an unrelated
|
|
4262
|
+
older row and forcing ``no_matching_event`` on every sample.
|
|
4263
|
+
"""
|
|
4264
|
+
_freeze_clock(mocker)
|
|
4265
|
+
skew = 16.0
|
|
4266
|
+
true_utc_event = _CLOCK_NOW_EPOCH + skew
|
|
4267
|
+
older_unrelated_event = _CLOCK_NOW_EPOCH - 30
|
|
4268
|
+
copied = pd.concat(
|
|
4269
|
+
[
|
|
4270
|
+
_copied_frame(older_unrelated_event, bid=9.9, ask=9.9),
|
|
4271
|
+
_copied_frame(true_utc_event),
|
|
4272
|
+
],
|
|
4273
|
+
ignore_index=True,
|
|
4274
|
+
)
|
|
4275
|
+
client = _clock_client(
|
|
4276
|
+
[_live_tick(true_utc_event + _UTC_PLUS_3)],
|
|
4277
|
+
pd.DataFrame(),
|
|
4278
|
+
)
|
|
4279
|
+
client.copy_ticks_range.side_effect = _windowed_copy_ticks_range(copied)
|
|
4280
|
+
normalizer = TickClockNormalizer(
|
|
4281
|
+
client,
|
|
4282
|
+
["SING30"],
|
|
4283
|
+
samples_per_symbol=1,
|
|
4284
|
+
min_agreeing_samples=1,
|
|
4285
|
+
)
|
|
4286
|
+
|
|
4287
|
+
calibration = normalizer.calibrate()
|
|
4288
|
+
|
|
4289
|
+
assert calibration.status == "calibrated"
|
|
4290
|
+
_assert_close(calibration.offset_seconds, _UTC_PLUS_3)
|
|
4291
|
+
assert client.copy_ticks_range.call_count == 1
|
|
4292
|
+
|
|
4293
|
+
def test_copied_event_beyond_future_skew_policy_is_not_matched(
|
|
4294
|
+
self,
|
|
4295
|
+
mocker: MockerFixture,
|
|
4296
|
+
) -> None:
|
|
4297
|
+
"""An event further ahead than the tolerated skew is never queried.
|
|
4298
|
+
|
|
4299
|
+
The widened upper bound is bounded, not open-ended: it matches
|
|
4300
|
+
``_MAX_FUTURE_SKEW_SECONDS``, the same policy enforced on normalized
|
|
4301
|
+
snapshots, so an event beyond that horizon is excluded from the
|
|
4302
|
+
copied-tick query and can never be silently accepted as a match.
|
|
4303
|
+
"""
|
|
4304
|
+
_freeze_clock(mocker)
|
|
4305
|
+
beyond_skew_event = _CLOCK_NOW_EPOCH + _MAX_FUTURE_SKEW_SECONDS + 1.0
|
|
4306
|
+
client = _clock_client(
|
|
4307
|
+
[_live_tick(beyond_skew_event + _UTC_PLUS_3)],
|
|
4308
|
+
pd.DataFrame(),
|
|
4309
|
+
)
|
|
4310
|
+
client.copy_ticks_range.side_effect = _windowed_copy_ticks_range(
|
|
4311
|
+
_copied_frame(beyond_skew_event),
|
|
4312
|
+
)
|
|
4313
|
+
normalizer = TickClockNormalizer(
|
|
4314
|
+
client,
|
|
4315
|
+
["SING30"],
|
|
4316
|
+
samples_per_symbol=1,
|
|
4317
|
+
min_agreeing_samples=1,
|
|
4318
|
+
)
|
|
4319
|
+
|
|
4320
|
+
calibration = normalizer.calibrate()
|
|
4321
|
+
|
|
4322
|
+
assert calibration.status == "no_copied_ticks"
|
|
4323
|
+
assert calibration.offset_seconds is None
|
|
4324
|
+
|
|
4220
4325
|
def test_matches_older_copied_row_when_newest_row_disagrees(
|
|
4221
4326
|
self,
|
|
4222
4327
|
mocker: MockerFixture,
|
|
@@ -10,6 +10,13 @@ required-markers = [
|
|
|
10
10
|
"sys_platform == 'win32'",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
+
[options]
|
|
14
|
+
exclude-newer = "2026-07-13T15:37:13.721821839Z"
|
|
15
|
+
exclude-newer-span = "P1W"
|
|
16
|
+
|
|
17
|
+
[options.exclude-newer-package]
|
|
18
|
+
pdmt5 = false
|
|
19
|
+
|
|
13
20
|
[[package]]
|
|
14
21
|
name = "annotated-doc"
|
|
15
22
|
version = "0.0.4"
|
|
@@ -499,7 +506,7 @@ wheels = [
|
|
|
499
506
|
|
|
500
507
|
[[package]]
|
|
501
508
|
name = "mt5cli"
|
|
502
|
-
version = "1.3.
|
|
509
|
+
version = "1.3.1"
|
|
503
510
|
source = { editable = "." }
|
|
504
511
|
dependencies = [
|
|
505
512
|
{ name = "pandas" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|