mt5cli 0.7.0__tar.gz → 0.7.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.
Files changed (41) hide show
  1. {mt5cli-0.7.0 → mt5cli-0.7.1}/PKG-INFO +1 -1
  2. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/cli.py +53 -52
  3. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/history.py +93 -50
  4. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/sdk.py +3 -0
  5. {mt5cli-0.7.0 → mt5cli-0.7.1}/pyproject.toml +1 -1
  6. mt5cli-0.7.1/tests/conftest.py +52 -0
  7. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/test_cli.py +0 -32
  8. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/test_sdk.py +0 -26
  9. {mt5cli-0.7.0 → mt5cli-0.7.1}/uv.lock +1 -1
  10. {mt5cli-0.7.0 → mt5cli-0.7.1}/.agents/skills/local-qa/SKILL.md +0 -0
  11. {mt5cli-0.7.0 → mt5cli-0.7.1}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  12. {mt5cli-0.7.0 → mt5cli-0.7.1}/.agents/skills/mt5cli/SKILL.md +0 -0
  13. {mt5cli-0.7.0 → mt5cli-0.7.1}/.claude/agents/codex.md +0 -0
  14. {mt5cli-0.7.0 → mt5cli-0.7.1}/.claude/settings.json +0 -0
  15. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/FUNDING.yml +0 -0
  16. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/dependabot.yml +0 -0
  17. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/renovate.json +0 -0
  18. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/workflows/ci.yml +0 -0
  19. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/workflows/claude.yml +0 -0
  20. {mt5cli-0.7.0 → mt5cli-0.7.1}/.github/workflows/release.yml +0 -0
  21. {mt5cli-0.7.0 → mt5cli-0.7.1}/.gitignore +0 -0
  22. {mt5cli-0.7.0 → mt5cli-0.7.1}/AGENTS.md +0 -0
  23. {mt5cli-0.7.0 → mt5cli-0.7.1}/CLAUDE.md +0 -0
  24. {mt5cli-0.7.0 → mt5cli-0.7.1}/LICENSE +0 -0
  25. {mt5cli-0.7.0 → mt5cli-0.7.1}/README.md +0 -0
  26. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/cli.md +0 -0
  27. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/history.md +0 -0
  28. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/index.md +0 -0
  29. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/sdk.md +0 -0
  30. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/trading.md +0 -0
  31. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/api/utils.md +0 -0
  32. {mt5cli-0.7.0 → mt5cli-0.7.1}/docs/index.md +0 -0
  33. {mt5cli-0.7.0 → mt5cli-0.7.1}/mkdocs.yml +0 -0
  34. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/__init__.py +0 -0
  35. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/__main__.py +0 -0
  36. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/trading.py +0 -0
  37. {mt5cli-0.7.0 → mt5cli-0.7.1}/mt5cli/utils.py +0 -0
  38. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/__init__.py +0 -0
  39. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/test_history.py +0 -0
  40. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/test_trading.py +0 -0
  41. {mt5cli-0.7.0 → mt5cli-0.7.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Command-line tool for MetaTrader 5
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5cli.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -96,6 +96,15 @@ def _sdk_client(ctx: typer.Context) -> sdk.Mt5CliClient:
96
96
  return sdk.Mt5CliClient(config=export_ctx.config)
97
97
 
98
98
 
99
+ def _export_command(
100
+ ctx: typer.Context,
101
+ fetch_fn: Callable[[sdk.Mt5CliClient], pd.DataFrame],
102
+ ) -> None:
103
+ """Create an SDK client, fetch a DataFrame, and export it."""
104
+ client = _sdk_client(ctx)
105
+ _execute_export(ctx, lambda: fetch_fn(client))
106
+
107
+
99
108
  @app.callback()
100
109
  def _callback( # pyright: ignore[reportUnusedFunction]
101
110
  ctx: typer.Context,
@@ -193,10 +202,9 @@ def rates_from(
193
202
  count: Annotated[int, typer.Option(help="Number of records.")],
194
203
  ) -> None:
195
204
  """Export rates from a start date."""
196
- client = _sdk_client(ctx)
197
- _execute_export(
205
+ _export_command(
198
206
  ctx,
199
- lambda: client.copy_rates_from(symbol, timeframe, date_from, count),
207
+ lambda client: client.copy_rates_from(symbol, timeframe, date_from, count),
200
208
  )
201
209
 
202
210
 
@@ -215,10 +223,14 @@ def rates_from_pos(
215
223
  count: Annotated[int, typer.Option(help="Number of records.")],
216
224
  ) -> None:
217
225
  """Export rates from a start position."""
218
- client = _sdk_client(ctx)
219
- _execute_export(
226
+ _export_command(
220
227
  ctx,
221
- lambda: client.copy_rates_from_pos(symbol, timeframe, start_pos, count),
228
+ lambda client: client.copy_rates_from_pos(
229
+ symbol,
230
+ timeframe,
231
+ start_pos,
232
+ count,
233
+ ),
222
234
  )
223
235
 
224
236
 
@@ -240,10 +252,14 @@ def latest_rates(
240
252
  ] = 0,
241
253
  ) -> None:
242
254
  """Export latest rates from a start position."""
243
- client = _sdk_client(ctx)
244
- _execute_export(
255
+ _export_command(
245
256
  ctx,
246
- lambda: client.latest_rates(symbol, timeframe, count, start_pos=start_pos),
257
+ lambda client: client.latest_rates(
258
+ symbol,
259
+ timeframe,
260
+ count,
261
+ start_pos=start_pos,
262
+ ),
247
263
  )
248
264
 
249
265
 
@@ -268,10 +284,9 @@ def rates_range(
268
284
  ],
269
285
  ) -> None:
270
286
  """Export rates for a date range."""
271
- client = _sdk_client(ctx)
272
- _execute_export(
287
+ _export_command(
273
288
  ctx,
274
- lambda: client.copy_rates_range(symbol, timeframe, date_from, date_to),
289
+ lambda client: client.copy_rates_range(symbol, timeframe, date_from, date_to),
275
290
  )
276
291
 
277
292
 
@@ -293,10 +308,9 @@ def ticks_from(
293
308
  ],
294
309
  ) -> None:
295
310
  """Export ticks from a start date."""
296
- client = _sdk_client(ctx)
297
- _execute_export(
311
+ _export_command(
298
312
  ctx,
299
- lambda: client.copy_ticks_from(symbol, date_from, count, flags),
313
+ lambda client: client.copy_ticks_from(symbol, date_from, count, flags),
300
314
  )
301
315
 
302
316
 
@@ -318,10 +332,9 @@ def ticks_range(
318
332
  ],
319
333
  ) -> None:
320
334
  """Export ticks for a date range."""
321
- client = _sdk_client(ctx)
322
- _execute_export(
335
+ _export_command(
323
336
  ctx,
324
- lambda: client.copy_ticks_range(symbol, date_from, date_to, flags),
337
+ lambda client: client.copy_ticks_range(symbol, date_from, date_to, flags),
325
338
  )
326
339
 
327
340
 
@@ -350,10 +363,9 @@ def ticks_recent(
350
363
  ] = "ALL", # pyright: ignore[reportArgumentType]
351
364
  ) -> None:
352
365
  """Export ticks from a recent time window."""
353
- client = _sdk_client(ctx)
354
- _execute_export(
366
+ _export_command(
355
367
  ctx,
356
- lambda: client.recent_ticks(
368
+ lambda client: client.recent_ticks(
357
369
  symbol,
358
370
  seconds,
359
371
  date_to=date_to,
@@ -366,13 +378,13 @@ def ticks_recent(
366
378
  @app.command()
367
379
  def account_info(ctx: typer.Context) -> None:
368
380
  """Export account information."""
369
- _execute_export(ctx, _sdk_client(ctx).account_info)
381
+ _export_command(ctx, lambda client: client.account_info())
370
382
 
371
383
 
372
384
  @app.command()
373
385
  def terminal_info(ctx: typer.Context) -> None:
374
386
  """Export terminal information."""
375
- _execute_export(ctx, _sdk_client(ctx).terminal_info)
387
+ _export_command(ctx, lambda client: client.terminal_info())
376
388
 
377
389
 
378
390
  @app.command()
@@ -384,8 +396,7 @@ def symbols(
384
396
  ] = None,
385
397
  ) -> None:
386
398
  """Export symbol list."""
387
- client = _sdk_client(ctx)
388
- _execute_export(ctx, lambda: client.symbols(group=group))
399
+ _export_command(ctx, lambda client: client.symbols(group=group))
389
400
 
390
401
 
391
402
  @app.command()
@@ -394,8 +405,7 @@ def symbol_info(
394
405
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
395
406
  ) -> None:
396
407
  """Export symbol details."""
397
- client = _sdk_client(ctx)
398
- _execute_export(ctx, lambda: client.symbol_info(symbol))
408
+ _export_command(ctx, lambda client: client.symbol_info(symbol))
399
409
 
400
410
 
401
411
  @app.command()
@@ -404,8 +414,7 @@ def minimum_margins(
404
414
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
405
415
  ) -> None:
406
416
  """Export minimum-volume buy and sell margin requirements."""
407
- client = _sdk_client(ctx)
408
- _execute_export(ctx, lambda: client.minimum_margins(symbol))
417
+ _export_command(ctx, lambda client: client.minimum_margins(symbol))
409
418
 
410
419
 
411
420
  @app.command()
@@ -416,10 +425,9 @@ def orders(
416
425
  ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
417
426
  ) -> None:
418
427
  """Export active orders."""
419
- client = _sdk_client(ctx)
420
- _execute_export(
428
+ _export_command(
421
429
  ctx,
422
- lambda: client.orders(symbol=symbol, group=group, ticket=ticket),
430
+ lambda client: client.orders(symbol=symbol, group=group, ticket=ticket),
423
431
  )
424
432
 
425
433
 
@@ -431,10 +439,9 @@ def positions(
431
439
  ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
432
440
  ) -> None:
433
441
  """Export open positions."""
434
- client = _sdk_client(ctx)
435
- _execute_export(
442
+ _export_command(
436
443
  ctx,
437
- lambda: client.positions(symbol=symbol, group=group, ticket=ticket),
444
+ lambda client: client.positions(symbol=symbol, group=group, ticket=ticket),
438
445
  )
439
446
 
440
447
 
@@ -455,10 +462,9 @@ def history_orders(
455
462
  position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
456
463
  ) -> None:
457
464
  """Export historical orders."""
458
- client = _sdk_client(ctx)
459
- _execute_export(
465
+ _export_command(
460
466
  ctx,
461
- lambda: client.history_orders(
467
+ lambda client: client.history_orders(
462
468
  date_from=date_from,
463
469
  date_to=date_to,
464
470
  group=group,
@@ -486,10 +492,9 @@ def history_deals(
486
492
  position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
487
493
  ) -> None:
488
494
  """Export historical deals."""
489
- client = _sdk_client(ctx)
490
- _execute_export(
495
+ _export_command(
491
496
  ctx,
492
- lambda: client.history_deals(
497
+ lambda client: client.history_deals(
493
498
  date_from=date_from,
494
499
  date_to=date_to,
495
500
  group=group,
@@ -512,10 +517,9 @@ def recent_history_deals(
512
517
  symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
513
518
  ) -> None:
514
519
  """Export historical deals from a recent trailing window."""
515
- client = _sdk_client(ctx)
516
- _execute_export(
520
+ _export_command(
517
521
  ctx,
518
- lambda: client.recent_history_deals(
522
+ lambda client: client.recent_history_deals(
519
523
  hours,
520
524
  date_to=date_to,
521
525
  group=group,
@@ -527,20 +531,19 @@ def recent_history_deals(
527
531
  @app.command()
528
532
  def mt5_summary(ctx: typer.Context) -> None:
529
533
  """Export a compact terminal/account status summary."""
530
- client = _sdk_client(ctx)
531
- _execute_export(ctx, client.mt5_summary_as_df)
534
+ _export_command(ctx, lambda client: client.mt5_summary_as_df())
532
535
 
533
536
 
534
537
  @app.command()
535
538
  def version(ctx: typer.Context) -> None:
536
539
  """Export MetaTrader5 version information."""
537
- _execute_export(ctx, _sdk_client(ctx).version)
540
+ _export_command(ctx, lambda client: client.version())
538
541
 
539
542
 
540
543
  @app.command()
541
544
  def last_error(ctx: typer.Context) -> None:
542
545
  """Export the last error information."""
543
- _execute_export(ctx, _sdk_client(ctx).last_error)
546
+ _export_command(ctx, lambda client: client.last_error())
544
547
 
545
548
 
546
549
  @app.command()
@@ -549,8 +552,7 @@ def symbol_info_tick(
549
552
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
550
553
  ) -> None:
551
554
  """Export the last tick for a symbol."""
552
- client = _sdk_client(ctx)
553
- _execute_export(ctx, lambda: client.symbol_info_tick(symbol))
555
+ _export_command(ctx, lambda client: client.symbol_info_tick(symbol))
554
556
 
555
557
 
556
558
  @app.command()
@@ -559,8 +561,7 @@ def market_book(
559
561
  symbol: Annotated[str, typer.Option(help="Symbol name.")],
560
562
  ) -> None:
561
563
  """Export market depth (order book) for a symbol."""
562
- client = _sdk_client(ctx)
563
- _execute_export(ctx, lambda: client.market_book(symbol))
564
+ _export_command(ctx, lambda client: client.market_book(symbol))
564
565
 
565
566
 
566
567
  @app.command()
@@ -1332,6 +1332,50 @@ def create_rate_compatibility_views(conn: sqlite3.Connection) -> None:
1332
1332
  )
1333
1333
 
1334
1334
 
1335
+ def _stream_symbol_frames(
1336
+ conn: sqlite3.Connection,
1337
+ symbols: Sequence[str],
1338
+ dataset: Dataset,
1339
+ if_exists: IfExists,
1340
+ written_columns: dict[Dataset, set[str]],
1341
+ fetch_frame: Callable[[str], pd.DataFrame],
1342
+ ) -> bool:
1343
+ """Stream per-symbol frames into SQLite.
1344
+
1345
+ Returns:
1346
+ True if the dataset table was written.
1347
+ """
1348
+ table_exists = False
1349
+ for sym in symbols:
1350
+ table_exists = write_streamed_frame(
1351
+ conn,
1352
+ fetch_frame(sym),
1353
+ dataset,
1354
+ table_exists,
1355
+ if_exists,
1356
+ written_columns,
1357
+ )
1358
+ return table_exists
1359
+
1360
+
1361
+ def _record_symbol_time_dedup(
1362
+ dedup_scopes: dict[Dataset, list[DedupScope]],
1363
+ written_tables: set[Dataset],
1364
+ dataset: Dataset,
1365
+ symbol: str,
1366
+ start_date: datetime,
1367
+ ) -> None:
1368
+ """Record a symbol-scoped deduplication window after an incremental write."""
1369
+ written_tables.add(dataset)
1370
+ _record_dedup_scope(
1371
+ dedup_scopes,
1372
+ dataset,
1373
+ "symbol = ? AND time >= ?",
1374
+ (symbol, start_date),
1375
+ frozenset({"symbol", "time"}),
1376
+ )
1377
+
1378
+
1335
1379
  def write_rates_dataset(
1336
1380
  conn: sqlite3.Connection,
1337
1381
  client: Mt5DataClient,
@@ -1347,8 +1391,8 @@ def write_rates_dataset(
1347
1391
  Returns:
1348
1392
  True if the rates table was written.
1349
1393
  """
1350
- table_exists = False
1351
- for sym in symbols:
1394
+
1395
+ def _fetch_rates_frame(sym: str) -> pd.DataFrame:
1352
1396
  frame = client.copy_rates_range_as_df(
1353
1397
  symbol=sym,
1354
1398
  timeframe=timeframe,
@@ -1358,15 +1402,16 @@ def write_rates_dataset(
1358
1402
  if len(frame.columns) != 0:
1359
1403
  frame.insert(0, "symbol", sym)
1360
1404
  frame.insert(1, "timeframe", timeframe)
1361
- table_exists = write_streamed_frame(
1362
- conn,
1363
- frame,
1364
- Dataset.rates,
1365
- table_exists,
1366
- if_exists,
1367
- written_columns,
1368
- )
1369
- return table_exists
1405
+ return frame
1406
+
1407
+ return _stream_symbol_frames(
1408
+ conn,
1409
+ symbols,
1410
+ Dataset.rates,
1411
+ if_exists,
1412
+ written_columns,
1413
+ _fetch_rates_frame,
1414
+ )
1370
1415
 
1371
1416
 
1372
1417
  def write_ticks_dataset(
@@ -1384,8 +1429,8 @@ def write_ticks_dataset(
1384
1429
  Returns:
1385
1430
  True if the ticks table was written.
1386
1431
  """
1387
- table_exists = False
1388
- for sym in symbols:
1432
+
1433
+ def _fetch_ticks_frame(sym: str) -> pd.DataFrame:
1389
1434
  frame = client.copy_ticks_range_as_df(
1390
1435
  symbol=sym,
1391
1436
  date_from=date_from,
@@ -1394,15 +1439,16 @@ def write_ticks_dataset(
1394
1439
  ).drop(columns=["symbol"], errors="ignore")
1395
1440
  if len(frame.columns) != 0:
1396
1441
  frame.insert(0, "symbol", sym)
1397
- table_exists = write_streamed_frame(
1398
- conn,
1399
- frame,
1400
- Dataset.ticks,
1401
- table_exists,
1402
- if_exists,
1403
- written_columns,
1404
- )
1405
- return table_exists
1442
+ return frame
1443
+
1444
+ return _stream_symbol_frames(
1445
+ conn,
1446
+ symbols,
1447
+ Dataset.ticks,
1448
+ if_exists,
1449
+ written_columns,
1450
+ _fetch_ticks_frame,
1451
+ )
1406
1452
 
1407
1453
 
1408
1454
  def write_history_dataset(
@@ -1437,22 +1483,22 @@ def write_history_dataset(
1437
1483
  if_exists,
1438
1484
  written_columns,
1439
1485
  )
1440
- for sym in symbols:
1441
- frame = fetch(date_from=date_from, date_to=date_to, symbol=sym)
1442
- frame = filter_trade_history_frame(
1443
- frame,
1486
+
1487
+ def _fetch_history_frame(sym: str) -> pd.DataFrame:
1488
+ return filter_trade_history_frame(
1489
+ fetch(date_from=date_from, date_to=date_to, symbol=sym),
1444
1490
  [sym],
1445
1491
  include_account_events=False,
1446
1492
  )
1447
- table_exists = write_streamed_frame(
1448
- conn,
1449
- frame,
1450
- dataset,
1451
- table_exists,
1452
- if_exists,
1453
- written_columns,
1454
- )
1455
- return table_exists
1493
+
1494
+ return _stream_symbol_frames(
1495
+ conn,
1496
+ symbols,
1497
+ dataset,
1498
+ if_exists,
1499
+ written_columns,
1500
+ _fetch_history_frame,
1501
+ )
1456
1502
 
1457
1503
 
1458
1504
  def _write_incremental_rates(
@@ -1525,13 +1571,12 @@ def _write_incremental_ticks(
1525
1571
  IfExists.APPEND,
1526
1572
  written_columns,
1527
1573
  ):
1528
- written_tables.add(Dataset.ticks)
1529
- _record_dedup_scope(
1574
+ _record_symbol_time_dedup(
1530
1575
  dedup_scopes,
1576
+ written_tables,
1531
1577
  Dataset.ticks,
1532
- "symbol = ? AND time >= ?",
1533
- (symbol, start_date),
1534
- frozenset({"symbol", "time"}),
1578
+ symbol,
1579
+ start_date,
1535
1580
  )
1536
1581
 
1537
1582
 
@@ -1564,13 +1609,12 @@ def _write_incremental_history_orders(
1564
1609
  written_columns,
1565
1610
  include_account_events=False,
1566
1611
  ):
1567
- written_tables.add(Dataset.history_orders)
1568
- _record_dedup_scope(
1612
+ _record_symbol_time_dedup(
1569
1613
  dedup_scopes,
1614
+ written_tables,
1570
1615
  Dataset.history_orders,
1571
- "symbol = ? AND time >= ?",
1572
- (symbol, start_date),
1573
- frozenset({"symbol", "time"}),
1616
+ symbol,
1617
+ start_date,
1574
1618
  )
1575
1619
 
1576
1620
 
@@ -1662,13 +1706,12 @@ def _write_incremental_history_deals(
1662
1706
  written_columns,
1663
1707
  include_account_events=False,
1664
1708
  ):
1665
- written_tables.add(Dataset.history_deals)
1666
- _record_dedup_scope(
1709
+ _record_symbol_time_dedup(
1667
1710
  dedup_scopes,
1711
+ written_tables,
1668
1712
  Dataset.history_deals,
1669
- "symbol = ? AND time >= ?",
1670
- (symbol, start_date),
1671
- frozenset({"symbol", "time"}),
1713
+ symbol,
1714
+ start_date,
1672
1715
  )
1673
1716
 
1674
1717
 
@@ -64,6 +64,9 @@ _MT5_HISTORY_CLIENT_CALL_FUNCTIONS: frozenset[str] = frozenset({
64
64
  "write_ticks_dataset",
65
65
  "write_history_dataset",
66
66
  "_write_incremental_history_deals",
67
+ "_fetch_rates_frame",
68
+ "_fetch_ticks_frame",
69
+ "_fetch_history_frame",
67
70
  })
68
71
  _NON_CALLABLE_TYPE_ERROR = re.compile(r"^'[^']+' object is not callable$")
69
72
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5cli"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  description = "Command-line tool for MetaTrader 5"
5
5
  authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
6
6
  maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
@@ -0,0 +1,52 @@
1
+ """Shared pytest fixtures for mt5cli tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import MagicMock
6
+
7
+ import pandas as pd
8
+ import pytest
9
+ from pytest_mock import MockerFixture # noqa: TC002
10
+
11
+ _DATAFRAME_METHODS = (
12
+ "copy_rates_from_as_df",
13
+ "copy_rates_from_pos_as_df",
14
+ "copy_rates_range_as_df",
15
+ "copy_ticks_from_as_df",
16
+ "copy_ticks_range_as_df",
17
+ "account_info_as_df",
18
+ "terminal_info_as_df",
19
+ "symbols_get_as_df",
20
+ "symbol_info_as_df",
21
+ "orders_get_as_df",
22
+ "positions_get_as_df",
23
+ "history_orders_get_as_df",
24
+ "history_deals_get_as_df",
25
+ "version_as_df",
26
+ "last_error_as_df",
27
+ "symbol_info_tick_as_df",
28
+ "market_book_get_as_df",
29
+ "order_check_as_df",
30
+ "order_send_as_df",
31
+ )
32
+
33
+
34
+ def build_mock_mt5_data_client() -> MagicMock:
35
+ """Return a MagicMock Mt5DataClient with common DataFrame stubs."""
36
+ client = MagicMock()
37
+ sample_df = pd.DataFrame({"col": [1]})
38
+ for method_name in _DATAFRAME_METHODS:
39
+ getattr(client, method_name).return_value = sample_df
40
+ client.version.return_value = (5, 0, 1)
41
+ client.terminal_info.return_value = {"connected": True, "paths": ["terminal.exe"]}
42
+ client.account_info.return_value = {"login": 123, "limits": {"modes": ["demo"]}}
43
+ client.symbols_total.return_value = 42
44
+ return client
45
+
46
+
47
+ @pytest.fixture
48
+ def mock_client(mocker: MockerFixture) -> MagicMock:
49
+ """Create and patch a mock Mt5DataClient for CLI and SDK tests."""
50
+ client = build_mock_mt5_data_client()
51
+ mocker.patch("mt5cli.sdk.Mt5DataClient", return_value=client)
52
+ return client
@@ -69,38 +69,6 @@ class TestExecuteExport:
69
69
  # ---------------------------------------------------------------------------
70
70
 
71
71
 
72
- @pytest.fixture
73
- def mock_client(mocker: MockerFixture) -> MagicMock:
74
- """Create and patch a mock Mt5DataClient for CLI tests."""
75
- client = MagicMock()
76
- sample_df = pd.DataFrame({"col": [1]})
77
- client.copy_rates_from_as_df.return_value = sample_df
78
- client.copy_rates_from_pos_as_df.return_value = sample_df
79
- client.copy_rates_range_as_df.return_value = sample_df
80
- client.copy_ticks_from_as_df.return_value = sample_df
81
- client.copy_ticks_range_as_df.return_value = sample_df
82
- client.account_info_as_df.return_value = sample_df
83
- client.terminal_info_as_df.return_value = sample_df
84
- client.symbols_get_as_df.return_value = sample_df
85
- client.symbol_info_as_df.return_value = sample_df
86
- client.orders_get_as_df.return_value = sample_df
87
- client.positions_get_as_df.return_value = sample_df
88
- client.history_orders_get_as_df.return_value = sample_df
89
- client.history_deals_get_as_df.return_value = sample_df
90
- client.version_as_df.return_value = sample_df
91
- client.last_error_as_df.return_value = sample_df
92
- client.symbol_info_tick_as_df.return_value = sample_df
93
- client.market_book_get_as_df.return_value = sample_df
94
- client.order_check_as_df.return_value = sample_df
95
- client.order_send_as_df.return_value = sample_df
96
- client.version.return_value = (5, 0, 1)
97
- client.terminal_info.return_value = {"connected": True, "paths": ["terminal.exe"]}
98
- client.account_info.return_value = {"login": 123, "limits": {"modes": ["demo"]}}
99
- client.symbols_total.return_value = 42
100
- mocker.patch("mt5cli.sdk.Mt5DataClient", return_value=client)
101
- return client
102
-
103
-
104
72
  class TestCommands:
105
73
  """Tests for all CLI subcommands via CliRunner."""
106
74
 
@@ -132,32 +132,6 @@ _DEALS_FIXTURE: dict[str, list[object]] = {
132
132
  }
133
133
 
134
134
 
135
- @pytest.fixture
136
- def mock_client(mocker: MockerFixture) -> MagicMock:
137
- """Create and patch a mock Mt5DataClient for SDK tests."""
138
- client = MagicMock()
139
- sample_df = pd.DataFrame({"col": [1]})
140
- client.copy_rates_from_as_df.return_value = sample_df
141
- client.copy_rates_from_pos_as_df.return_value = sample_df
142
- client.copy_rates_range_as_df.return_value = sample_df
143
- client.copy_ticks_from_as_df.return_value = sample_df
144
- client.copy_ticks_range_as_df.return_value = sample_df
145
- client.account_info_as_df.return_value = sample_df
146
- client.terminal_info_as_df.return_value = sample_df
147
- client.symbols_get_as_df.return_value = sample_df
148
- client.symbol_info_as_df.return_value = sample_df
149
- client.orders_get_as_df.return_value = sample_df
150
- client.positions_get_as_df.return_value = sample_df
151
- client.history_orders_get_as_df.return_value = sample_df
152
- client.history_deals_get_as_df.return_value = sample_df
153
- client.version_as_df.return_value = sample_df
154
- client.last_error_as_df.return_value = sample_df
155
- client.symbol_info_tick_as_df.return_value = sample_df
156
- client.market_book_get_as_df.return_value = sample_df
157
- mocker.patch("mt5cli.sdk.Mt5DataClient", return_value=client)
158
- return client
159
-
160
-
161
135
  def _build_history_client(mocker: MockerFixture) -> MagicMock:
162
136
  """Build a mocked Mt5DataClient with per-symbol history results."""
163
137
  client = MagicMock()
@@ -487,7 +487,7 @@ wheels = [
487
487
 
488
488
  [[package]]
489
489
  name = "mt5cli"
490
- version = "0.7.0"
490
+ version = "0.7.1"
491
491
  source = { editable = "." }
492
492
  dependencies = [
493
493
  { name = "click" },
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