mt5cli 1.2.4__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.2.4 → mt5cli-1.3.1}/.github/workflows/ci.yml +1 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/PKG-INFO +1 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/public-contract.md +4 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/trading.md +108 -7
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/__init__.py +5 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/contract.py +5 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/history.py +34 -6
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/trading.py +706 -59
- {mt5cli-1.2.4 → mt5cli-1.3.1}/pyproject.toml +3 -1
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_cli.py +1 -3
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_history.py +165 -9
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_trading.py +1003 -75
- {mt5cli-1.2.4 → mt5cli-1.3.1}/uv.lock +9 -2
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.agents/skills/mt5cli/SKILL.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.claude/agents/codex.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.claude/settings.json +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.github/FUNDING.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.github/dependabot.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.github/renovate.json +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.github/workflows/claude.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.github/workflows/release.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/.gitignore +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/AGENTS.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/CLAUDE.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/LICENSE +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/README.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/cli.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/client.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/converters.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/exceptions.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/grafana.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/history.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/index.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/marketdata.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/observability.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/schemas.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/telemetry.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/api/utils.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/docs/index.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/README.md +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/compose.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-market.json +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mkdocs.yml +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/__main__.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/cli.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/client.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/converters.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/exceptions.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/grafana.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/marketdata.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/observability.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/retry.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/schemas.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/telemetry.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/mt5cli/utils.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/__init__.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/conftest.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_client.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_contracts.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_examples.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_grafana.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_marketdata.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_observability.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_packaging.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_telemetry.py +0 -0
- {mt5cli-1.2.4 → mt5cli-1.3.1}/tests/test_utils.py +0 -0
|
@@ -62,7 +62,7 @@ jobs:
|
|
|
62
62
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6.0.2
|
|
63
63
|
with:
|
|
64
64
|
persist-credentials: false
|
|
65
|
-
- uses: astral-sh/setup-uv@
|
|
65
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
66
66
|
- name: Check CLI help with minimum Click and Typer versions
|
|
67
67
|
run: uv run --with click==8.4.0 --with typer==0.26.0 mt5cli --help
|
|
68
68
|
python-docs-deploy:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mt5cli
|
|
3
|
-
Version: 1.
|
|
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>
|
|
@@ -102,7 +102,10 @@ 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
|
-
| `
|
|
105
|
+
| `TickClockNormalizer` | Connection-scoped, copied-tick-validated UTC tick normalization |
|
|
106
|
+
| `TickClockCalibration` | Server-clock calibration diagnostics (offset, evidence, status) |
|
|
107
|
+
| `NormalizedTickSnapshot` | Typed latest-tick contract with raw vs validated UTC timestamps |
|
|
108
|
+
| `CalibrationStatus`, `ClockStatus` | Literal status types for clock calibration and normalization |
|
|
106
109
|
| `detect_position_side` | Net long / short / flat from open positions |
|
|
107
110
|
| `calculate_spread_ratio` | Relative bid-ask spread |
|
|
108
111
|
| `calculate_margin_and_volume`, `calculate_volume_by_margin`, `calculate_new_position_margin_ratio` | Margin budget and volume sizing |
|
|
@@ -215,14 +215,115 @@ Any broker-specific offset handling therefore requires evidence from the live
|
|
|
215
215
|
data source in use and must be applied separately. Do not subtract a presumed
|
|
216
216
|
UTC+2 or UTC+3 offset from timestamps that are already UTC.
|
|
217
217
|
|
|
218
|
-
`estimate_server_clock_offset_seconds()` reads the latest tick for a symbol
|
|
219
|
-
and returns the broker's clock offset from true UTC, rounded to the nearest
|
|
220
|
-
half hour, or `None` when no valid tick time is available. Use it to validate a
|
|
221
|
-
suspected offset for the live `symbol_info_tick()` source before applying the
|
|
222
|
-
result in downstream time-window calculations.
|
|
223
|
-
|
|
224
218
|
No offset is applied automatically by history retrieval helpers.
|
|
225
219
|
|
|
220
|
+
## UTC normalization for live ticks
|
|
221
|
+
|
|
222
|
+
`TickClockNormalizer` provides the stable API for obtaining a latest tick with
|
|
223
|
+
a **validated** UTC timestamp, so strategy applications never compare a raw
|
|
224
|
+
server-labeled tick time against `datetime.now(UTC)` themselves.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from mt5cli import TickClockNormalizer, mt5_session
|
|
228
|
+
|
|
229
|
+
with mt5_session() as client:
|
|
230
|
+
normalizer = TickClockNormalizer(client, ["SING30", "EURUSD"])
|
|
231
|
+
snapshot = normalizer.get_normalized_tick_snapshot("SING30")
|
|
232
|
+
if snapshot["clock_status"] == "calibrated":
|
|
233
|
+
freshness = now_utc - snapshot["time_utc"] # safe wall-clock compare
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
A normalized snapshot keeps the raw/UTC distinction explicit:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
{
|
|
240
|
+
"symbol": "SING30",
|
|
241
|
+
"bid": ...,
|
|
242
|
+
"ask": ...,
|
|
243
|
+
"last": ...,
|
|
244
|
+
"volume": ...,
|
|
245
|
+
"raw_time": 1717243200, # numeric MT5 epoch, as labeled by broker
|
|
246
|
+
"time_utc": datetime(..., tzinfo=UTC), # None unless calibrated
|
|
247
|
+
"server_clock_offset_seconds": 10800.0, # None unless calibrated
|
|
248
|
+
"clock_status": "calibrated", # or "uncalibrated"
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Calibration evidence
|
|
253
|
+
|
|
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.
|
|
263
|
+
|
|
264
|
+
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
|
|
267
|
+
cannot self-confirm. Evidence can come from multiple samples of one actively
|
|
268
|
+
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.
|
|
281
|
+
|
|
282
|
+
### Failure modes and fail-closed behavior
|
|
283
|
+
|
|
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
|
|
289
|
+
`"uncalibrated"`, `time_utc` and `server_clock_offset_seconds` are `None`,
|
|
290
|
+
and the raw fields remain available. A raw timestamp is never silently
|
|
291
|
+
treated as UTC. `normalizer.calibration` exposes the full
|
|
292
|
+
`TickClockCalibration` diagnostics: status, offset, distinct sample count,
|
|
293
|
+
evidence symbols, and last calibration time (`to_dict()` for serialization).
|
|
294
|
+
|
|
295
|
+
### Caching and revalidation
|
|
296
|
+
|
|
297
|
+
Calibration is a property of the broker server clock, so it is cached on the
|
|
298
|
+
normalizer (keep one instance per MT5 connection/account) and reused across
|
|
299
|
+
symbols and calls. A cached offset is recomputed in three cases:
|
|
300
|
+
|
|
301
|
+
- After `max_calibration_age_seconds` (default 6 hours, bounding
|
|
302
|
+
DST-transition staleness), unconditionally.
|
|
303
|
+
- Immediately when a normalized timestamp lands more than two minutes in the
|
|
304
|
+
future — evidence that the broker offset grew, e.g. a UTC+2 to UTC+3
|
|
305
|
+
transition.
|
|
306
|
+
- 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.
|
|
312
|
+
|
|
313
|
+
If recalibration still cannot validate the timestamp, the snapshot fails
|
|
314
|
+
closed. Failed calibrations are recorded for diagnostics but never reused;
|
|
315
|
+
retries are bounded to at most once per `failed_calibration_retry_seconds`
|
|
316
|
+
(default 30 seconds) rather than on every call, so a closed or illiquid
|
|
317
|
+
market does not trigger a full resample on every snapshot request.
|
|
318
|
+
|
|
319
|
+
A genuinely stale tick (weekend, illiquid symbol) under a valid calibration
|
|
320
|
+
normalizes to its true **past** UTC instant — staleness is preserved, never
|
|
321
|
+
corrected to the present.
|
|
322
|
+
|
|
323
|
+
mt5cli owns MT5 timestamp normalization only. Freshness thresholds,
|
|
324
|
+
entry-blocking rules, and other strategy policy belong in downstream
|
|
325
|
+
applications.
|
|
326
|
+
|
|
226
327
|
## Migration from application-local helpers
|
|
227
328
|
|
|
228
329
|
| Application-local concern | mt5cli replacement |
|
|
@@ -234,7 +335,7 @@ No offset is applied automatically by history retrieval helpers.
|
|
|
234
335
|
| Local order or position margin estimation | `estimate_order_margin()`, `calculate_positions_margin()` |
|
|
235
336
|
| Local closed-bar fetch from a session | `fetch_latest_closed_rates()` or `fetch_latest_closed_rates_indexed()` |
|
|
236
337
|
| Local recent deal history fetch from a session | `client.recent_history_deals()` |
|
|
237
|
-
| Local broker server clock offset measurement | `
|
|
338
|
+
| Local broker server clock offset measurement | `TickClockNormalizer` |
|
|
238
339
|
| Local SL/TP price derivation | `determine_order_limits()` |
|
|
239
340
|
| Throttled SQLite history loop with ad-hoc error handling | `ThrottledHistoryUpdater(suppress_errors=True)` |
|
|
240
341
|
|
|
@@ -40,8 +40,11 @@ from .marketdata import (
|
|
|
40
40
|
)
|
|
41
41
|
from .observability import update_observability, update_observability_with_config
|
|
42
42
|
from .trading import (
|
|
43
|
+
CalibrationStatus,
|
|
44
|
+
ClockStatus,
|
|
43
45
|
ExecutionStatus,
|
|
44
46
|
MarginVolume,
|
|
47
|
+
NormalizedTickSnapshot,
|
|
45
48
|
OrderExecutionResult,
|
|
46
49
|
OrderFillingMode,
|
|
47
50
|
OrderLimits,
|
|
@@ -49,6 +52,8 @@ from .trading import (
|
|
|
49
52
|
OrderTimeMode,
|
|
50
53
|
PositionSide,
|
|
51
54
|
ProjectionMode,
|
|
55
|
+
TickClockCalibration,
|
|
56
|
+
TickClockNormalizer,
|
|
52
57
|
calculate_account_projected_margin_ratio,
|
|
53
58
|
calculate_margin_and_volume,
|
|
54
59
|
calculate_new_position_margin_ratio,
|
|
@@ -65,7 +70,6 @@ from .trading import (
|
|
|
65
70
|
determine_order_limits,
|
|
66
71
|
ensure_symbol_selected,
|
|
67
72
|
estimate_order_margin,
|
|
68
|
-
estimate_server_clock_offset_seconds,
|
|
69
73
|
extract_tick_price,
|
|
70
74
|
fetch_latest_closed_rates_indexed,
|
|
71
75
|
get_account_snapshot,
|
|
@@ -112,10 +112,15 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
|
|
112
112
|
"OrderTimeMode",
|
|
113
113
|
"PositionSide",
|
|
114
114
|
"ProjectionMode",
|
|
115
|
+
"CalibrationStatus",
|
|
116
|
+
"ClockStatus",
|
|
115
117
|
"ExecutionStatus",
|
|
116
118
|
"MarginVolume",
|
|
119
|
+
"NormalizedTickSnapshot",
|
|
117
120
|
"OrderExecutionResult",
|
|
118
121
|
"OrderLimits",
|
|
122
|
+
"TickClockCalibration",
|
|
123
|
+
"TickClockNormalizer",
|
|
119
124
|
"RateTarget",
|
|
120
125
|
"ThrottledHistoryUpdater",
|
|
121
126
|
"build_config",
|
|
@@ -141,7 +146,6 @@ STABLE_SDK_EXPORTS: frozenset[str] = frozenset({
|
|
|
141
146
|
"drop_forming_rate_bar",
|
|
142
147
|
"ensure_symbol_selected",
|
|
143
148
|
"estimate_order_margin",
|
|
144
|
-
"estimate_server_clock_offset_seconds",
|
|
145
149
|
"extract_tick_price",
|
|
146
150
|
"fetch_latest_closed_rates",
|
|
147
151
|
"fetch_latest_closed_rates_indexed",
|
|
@@ -51,6 +51,10 @@ DEFAULT_HISTORY_DATASETS: frozenset[Dataset] = frozenset({
|
|
|
51
51
|
Dataset.history_orders,
|
|
52
52
|
Dataset.history_deals,
|
|
53
53
|
})
|
|
54
|
+
_EXPECTED_EMPTY_HISTORY_DATASETS: frozenset[Dataset] = frozenset({
|
|
55
|
+
Dataset.history_orders,
|
|
56
|
+
Dataset.history_deals,
|
|
57
|
+
})
|
|
54
58
|
|
|
55
59
|
_HISTORY_DEDUP_KEYS: dict[Dataset, tuple[tuple[str, ...], ...]] = {
|
|
56
60
|
Dataset.rates: DEDUP_KEYS[DataKind.rates],
|
|
@@ -1341,11 +1345,20 @@ def get_incremental_start_datetime(
|
|
|
1341
1345
|
return starts[symbol, timeframe]
|
|
1342
1346
|
|
|
1343
1347
|
|
|
1348
|
+
def _empty_dataset_log_level(dataset: Dataset) -> int:
|
|
1349
|
+
"""Return the log level for a schema-less dataset result."""
|
|
1350
|
+
if dataset in _EXPECTED_EMPTY_HISTORY_DATASETS:
|
|
1351
|
+
return logging.INFO
|
|
1352
|
+
return logging.WARNING
|
|
1353
|
+
|
|
1354
|
+
|
|
1344
1355
|
def append_dataframe(
|
|
1345
1356
|
conn: sqlite3.Connection,
|
|
1346
1357
|
frame: pd.DataFrame,
|
|
1347
1358
|
table_name: str,
|
|
1348
1359
|
if_exists: IfExists,
|
|
1360
|
+
*,
|
|
1361
|
+
empty_log_level: int = logging.WARNING,
|
|
1349
1362
|
) -> bool:
|
|
1350
1363
|
"""Append a DataFrame to SQLite when it has a schema.
|
|
1351
1364
|
|
|
@@ -1353,7 +1366,11 @@ def append_dataframe(
|
|
|
1353
1366
|
True if a table was written, False if the frame had no columns.
|
|
1354
1367
|
"""
|
|
1355
1368
|
if len(frame.columns) == 0:
|
|
1356
|
-
logger.
|
|
1369
|
+
logger.log(
|
|
1370
|
+
empty_log_level,
|
|
1371
|
+
"Skipping %s: dataset returned no columns",
|
|
1372
|
+
table_name,
|
|
1373
|
+
)
|
|
1357
1374
|
return False
|
|
1358
1375
|
writable = _canonicalize_sqlite_time_columns(frame)
|
|
1359
1376
|
writable.to_sql( # type: ignore[reportUnknownMemberType]
|
|
@@ -1409,7 +1426,13 @@ def write_streamed_frame(
|
|
|
1409
1426
|
True if the dataset table exists after this write attempt.
|
|
1410
1427
|
"""
|
|
1411
1428
|
write_mode = IfExists.APPEND if table_exists else if_exists
|
|
1412
|
-
if append_dataframe(
|
|
1429
|
+
if append_dataframe(
|
|
1430
|
+
conn,
|
|
1431
|
+
frame,
|
|
1432
|
+
dataset.table_name,
|
|
1433
|
+
write_mode,
|
|
1434
|
+
empty_log_level=_empty_dataset_log_level(dataset),
|
|
1435
|
+
):
|
|
1413
1436
|
record_written_columns(written_columns, dataset, frame)
|
|
1414
1437
|
return True
|
|
1415
1438
|
return table_exists
|
|
@@ -1751,7 +1774,8 @@ def _stream_symbol_frames(
|
|
|
1751
1774
|
for sym in symbols:
|
|
1752
1775
|
frame = fetch_frame(sym)
|
|
1753
1776
|
if len(frame.columns) == 0:
|
|
1754
|
-
logger.
|
|
1777
|
+
logger.log(
|
|
1778
|
+
_empty_dataset_log_level(dataset),
|
|
1755
1779
|
"Skipping %s for symbol=%s: dataset returned no columns",
|
|
1756
1780
|
dataset.table_name,
|
|
1757
1781
|
sym,
|
|
@@ -2243,8 +2267,8 @@ def _finalize_incremental_writes(
|
|
|
2243
2267
|
Dataset.history_deals not in written_columns
|
|
2244
2268
|
and Dataset.history_deals not in written_tables
|
|
2245
2269
|
):
|
|
2246
|
-
logger.
|
|
2247
|
-
"
|
|
2270
|
+
logger.info(
|
|
2271
|
+
"Skipping history-deal views: no history_deals data was available",
|
|
2248
2272
|
)
|
|
2249
2273
|
|
|
2250
2274
|
|
|
@@ -2848,9 +2872,13 @@ def collect_history(
|
|
|
2848
2872
|
conn,
|
|
2849
2873
|
written_columns[Dataset.history_deals],
|
|
2850
2874
|
)
|
|
2875
|
+
elif with_views and Dataset.history_deals in selected:
|
|
2876
|
+
logger.info(
|
|
2877
|
+
"Skipping history-deal views: no history_deals data was collected",
|
|
2878
|
+
)
|
|
2851
2879
|
elif with_views:
|
|
2852
2880
|
logger.warning(
|
|
2853
|
-
"--with-views
|
|
2881
|
+
"--with-views requires the history_deals dataset",
|
|
2854
2882
|
)
|
|
2855
2883
|
logger.info(
|
|
2856
2884
|
"Collected %s for %d symbol(s) into %s",
|