pdmt5 0.1.9__tar.gz → 0.2.0__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.
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.github/workflows/ci.yml +4 -13
- pdmt5-0.2.0/.github/workflows/pr-agent.yml +34 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/PKG-INFO +2 -3
- {pdmt5-0.1.9 → pdmt5-0.2.0}/README.md +1 -2
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pdmt5/trading.py +70 -15
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pyproject.toml +1 -1
- {pdmt5-0.1.9 → pdmt5-0.2.0}/uv.lock +2 -2
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.claude/settings.json +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.github/FUNDING.yml +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.github/copilot-instructions.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.github/dependabot.yml +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.github/workflows/claude.yml +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/.gitignore +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/CLAUDE.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/LICENSE +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/api/dataframe.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/api/index.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/api/mt5.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/api/trading.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/api/utils.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/docs/index.md +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/mkdocs.yml +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pdmt5/__init__.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pdmt5/dataframe.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pdmt5/mt5.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/pdmt5/utils.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/renovate.json +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/__init__.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/test_dataframe.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/test_init.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/test_mt5.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/test_trading.py +0 -0
- {pdmt5-0.1.9 → pdmt5-0.2.0}/tests/test_utils.py +0 -0
|
@@ -30,25 +30,16 @@ jobs:
|
|
|
30
30
|
uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-lint-and-scan.yml@main
|
|
31
31
|
with:
|
|
32
32
|
package-path: .
|
|
33
|
-
python-version: 3.x
|
|
34
33
|
runs-on: windows-latest
|
|
35
34
|
python-test:
|
|
36
35
|
if: >
|
|
37
36
|
github.event_name == 'push'
|
|
38
37
|
|| github.event_name == 'pull_request'
|
|
39
38
|
|| (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test')
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
- name: Set up uv
|
|
45
|
-
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
|
46
|
-
- name: Install the package
|
|
47
|
-
run: >
|
|
48
|
-
uv sync
|
|
49
|
-
- name: Run unit tests with pytest
|
|
50
|
-
run: >
|
|
51
|
-
uv run pytest
|
|
39
|
+
uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-test.yml@main
|
|
40
|
+
with:
|
|
41
|
+
package-path: .
|
|
42
|
+
runs-on: windows-latest
|
|
52
43
|
dependabot-auto-merge:
|
|
53
44
|
if: >
|
|
54
45
|
github.event_name == 'pull_request' && github.actor == 'dependabot[bot]'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: PR-agent
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types:
|
|
6
|
+
- opened
|
|
7
|
+
- reopened
|
|
8
|
+
- ready_for_review
|
|
9
|
+
issue_comment:
|
|
10
|
+
types:
|
|
11
|
+
- created
|
|
12
|
+
- edited
|
|
13
|
+
- deleted
|
|
14
|
+
jobs:
|
|
15
|
+
pr-agent:
|
|
16
|
+
if: >
|
|
17
|
+
github.event.sender.type != 'Bot'
|
|
18
|
+
&& (
|
|
19
|
+
github.event_name == 'pull_request'
|
|
20
|
+
|| github.event_name == 'issue_comment'
|
|
21
|
+
)
|
|
22
|
+
uses: dceoy/gh-actions-for-devops/.github/workflows/pr-agent.yml@main
|
|
23
|
+
permissions:
|
|
24
|
+
contents: write
|
|
25
|
+
pull-requests: write
|
|
26
|
+
issues: write
|
|
27
|
+
id-token: write
|
|
28
|
+
with:
|
|
29
|
+
auto-describe: true
|
|
30
|
+
auto-review: true
|
|
31
|
+
auto-improve: true
|
|
32
|
+
secrets:
|
|
33
|
+
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
34
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdmt5
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Pandas-based data handler for MetaTrader 5
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/pdmt5.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -56,8 +56,7 @@ Pandas-based data handler for MetaTrader 5
|
|
|
56
56
|
### Using pip
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
pip install -U pdmt5
|
|
60
|
-
pip install -U MetaTrader5
|
|
59
|
+
pip install -U pdmt5 MetaTrader5
|
|
61
60
|
```
|
|
62
61
|
|
|
63
62
|
### Using uv
|
|
@@ -243,6 +243,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
243
243
|
Returns:
|
|
244
244
|
Dictionary with operation result.
|
|
245
245
|
"""
|
|
246
|
+
self.logger.info("Placing market order: %s %s %s", order_side, volume, symbol)
|
|
246
247
|
return self._send_or_check_order(
|
|
247
248
|
request={
|
|
248
249
|
"action": self.mt5.TRADE_ACTION_DEAL,
|
|
@@ -315,6 +316,13 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
315
316
|
if sl != p["sl"] or tp != p["tp"]
|
|
316
317
|
]
|
|
317
318
|
if order_requests:
|
|
319
|
+
self.logger.info(
|
|
320
|
+
"Updating SL/TP for %d positions for %s: %s/%s",
|
|
321
|
+
len(order_requests),
|
|
322
|
+
symbol,
|
|
323
|
+
sl,
|
|
324
|
+
tp,
|
|
325
|
+
)
|
|
318
326
|
return [
|
|
319
327
|
self._send_or_check_order(request=r, dry_run=dry_run)
|
|
320
328
|
for r in order_requests
|
|
@@ -354,15 +362,22 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
354
362
|
else symbol_info_tick["ask"]
|
|
355
363
|
),
|
|
356
364
|
)
|
|
365
|
+
result = {"volume": symbol_info["volume_min"], "margin": margin}
|
|
357
366
|
if margin:
|
|
358
|
-
|
|
367
|
+
self.logger.info(
|
|
368
|
+
"Calculated minimum %s order margin for %s: %s",
|
|
369
|
+
order_side,
|
|
370
|
+
symbol,
|
|
371
|
+
result,
|
|
372
|
+
)
|
|
359
373
|
else:
|
|
360
374
|
self.logger.warning(
|
|
361
|
-
"
|
|
362
|
-
symbol,
|
|
375
|
+
"Calculated minimum order margin to %s %s: %s",
|
|
363
376
|
order_side,
|
|
377
|
+
symbol,
|
|
378
|
+
result,
|
|
364
379
|
)
|
|
365
|
-
|
|
380
|
+
return result
|
|
366
381
|
|
|
367
382
|
def calculate_volume_by_margin(
|
|
368
383
|
self,
|
|
@@ -385,12 +400,19 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
385
400
|
order_side=order_side,
|
|
386
401
|
)
|
|
387
402
|
if min_order_margin_dict["margin"]:
|
|
388
|
-
|
|
403
|
+
result = (
|
|
389
404
|
floor(margin / min_order_margin_dict["margin"])
|
|
390
405
|
* min_order_margin_dict["volume"]
|
|
391
406
|
)
|
|
392
407
|
else:
|
|
393
|
-
|
|
408
|
+
result = 0.0
|
|
409
|
+
self.logger.info(
|
|
410
|
+
"Calculated volume by margin to %s %s: %s",
|
|
411
|
+
order_side,
|
|
412
|
+
symbol,
|
|
413
|
+
result,
|
|
414
|
+
)
|
|
415
|
+
return result
|
|
394
416
|
|
|
395
417
|
def calculate_spread_ratio(
|
|
396
418
|
self,
|
|
@@ -405,11 +427,13 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
405
427
|
Spread ratio as a float.
|
|
406
428
|
"""
|
|
407
429
|
symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
|
|
408
|
-
|
|
430
|
+
result = (
|
|
409
431
|
(symbol_info_tick["ask"] - symbol_info_tick["bid"])
|
|
410
432
|
/ (symbol_info_tick["ask"] + symbol_info_tick["bid"])
|
|
411
433
|
* 2
|
|
412
434
|
)
|
|
435
|
+
self.logger.info("Calculated spread ratio for %s: %s", symbol, result)
|
|
436
|
+
return result
|
|
413
437
|
|
|
414
438
|
def fetch_latest_rates_as_df(
|
|
415
439
|
self,
|
|
@@ -440,13 +464,20 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
440
464
|
)
|
|
441
465
|
raise Mt5TradingError(error_message) from e
|
|
442
466
|
else:
|
|
443
|
-
|
|
467
|
+
result = self.copy_rates_from_pos_as_df(
|
|
444
468
|
symbol=symbol,
|
|
445
469
|
timeframe=timeframe,
|
|
446
470
|
start_pos=0,
|
|
447
471
|
count=count,
|
|
448
472
|
index_keys=index_keys,
|
|
449
473
|
)
|
|
474
|
+
self.logger.info(
|
|
475
|
+
"Fetched latest %s rates for %s: %d rows",
|
|
476
|
+
granularity,
|
|
477
|
+
symbol,
|
|
478
|
+
result.shape[0],
|
|
479
|
+
)
|
|
480
|
+
return result
|
|
450
481
|
|
|
451
482
|
def fetch_latest_ticks_as_df(
|
|
452
483
|
self,
|
|
@@ -465,13 +496,19 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
465
496
|
pd.DataFrame: Tick data with time index.
|
|
466
497
|
"""
|
|
467
498
|
last_tick_time = self.symbol_info_tick_as_dict(symbol=symbol)["time"]
|
|
468
|
-
|
|
499
|
+
result = self.copy_ticks_range_as_df(
|
|
469
500
|
symbol=symbol,
|
|
470
501
|
date_from=(last_tick_time - timedelta(seconds=seconds)),
|
|
471
502
|
date_to=(last_tick_time + timedelta(seconds=seconds)),
|
|
472
503
|
flags=self.mt5.COPY_TICKS_ALL,
|
|
473
504
|
index_keys=index_keys,
|
|
474
505
|
)
|
|
506
|
+
self.logger.info(
|
|
507
|
+
"Fetched latest ticks for %s: %d rows",
|
|
508
|
+
symbol,
|
|
509
|
+
result.shape[0],
|
|
510
|
+
)
|
|
511
|
+
return result
|
|
475
512
|
|
|
476
513
|
def collect_entry_deals_as_df(
|
|
477
514
|
self,
|
|
@@ -497,14 +534,20 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
497
534
|
index_keys=index_keys,
|
|
498
535
|
)
|
|
499
536
|
if deals_df.empty:
|
|
500
|
-
|
|
537
|
+
result = deals_df
|
|
501
538
|
else:
|
|
502
|
-
|
|
539
|
+
result = deals_df.pipe(
|
|
503
540
|
lambda d: d[
|
|
504
541
|
d["entry"]
|
|
505
542
|
& d["type"].isin({self.mt5.DEAL_TYPE_BUY, self.mt5.DEAL_TYPE_SELL})
|
|
506
543
|
]
|
|
507
544
|
)
|
|
545
|
+
self.logger.info(
|
|
546
|
+
"Collected entry deals for %s: %d rows",
|
|
547
|
+
symbol,
|
|
548
|
+
result.shape[0],
|
|
549
|
+
)
|
|
550
|
+
return result
|
|
508
551
|
|
|
509
552
|
def fetch_positions_with_metrics_as_df(
|
|
510
553
|
self,
|
|
@@ -520,7 +563,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
520
563
|
"""
|
|
521
564
|
positions_df = self.positions_get_as_df(symbol=symbol)
|
|
522
565
|
if positions_df.empty:
|
|
523
|
-
|
|
566
|
+
result = positions_df
|
|
524
567
|
else:
|
|
525
568
|
symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
|
|
526
569
|
ask_margin = self.order_calc_margin(
|
|
@@ -535,7 +578,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
535
578
|
volume=1,
|
|
536
579
|
price=symbol_info_tick["bid"],
|
|
537
580
|
)
|
|
538
|
-
|
|
581
|
+
result = (
|
|
539
582
|
positions_df.assign(
|
|
540
583
|
elapsed_seconds=lambda d: (
|
|
541
584
|
symbol_info_tick["time"] - d["time"]
|
|
@@ -566,6 +609,12 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
566
609
|
)
|
|
567
610
|
.drop(columns=["buy_i", "sell_i", "sign", "underlier_increase_ratio"])
|
|
568
611
|
)
|
|
612
|
+
self.logger.info(
|
|
613
|
+
"Fetched positions with metrics for %s: %d rows",
|
|
614
|
+
symbol,
|
|
615
|
+
result.shape[0],
|
|
616
|
+
)
|
|
617
|
+
return result
|
|
569
618
|
|
|
570
619
|
def calculate_new_position_margin_ratio(
|
|
571
620
|
self,
|
|
@@ -585,7 +634,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
585
634
|
"""
|
|
586
635
|
account_info = self.account_info_as_dict()
|
|
587
636
|
if not account_info["equity"]:
|
|
588
|
-
|
|
637
|
+
result = 0.0
|
|
589
638
|
else:
|
|
590
639
|
positions_df = self.fetch_positions_with_metrics_as_df(symbol=symbol)
|
|
591
640
|
current_signed_margin = (
|
|
@@ -610,6 +659,12 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
610
659
|
)
|
|
611
660
|
else:
|
|
612
661
|
new_signed_margin = 0
|
|
613
|
-
|
|
662
|
+
result = abs(
|
|
614
663
|
(new_signed_margin + current_signed_margin) / account_info["equity"]
|
|
615
664
|
)
|
|
665
|
+
self.logger.info(
|
|
666
|
+
"Calculated new position margin ratio for %s: %s",
|
|
667
|
+
symbol,
|
|
668
|
+
result,
|
|
669
|
+
)
|
|
670
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pdmt5"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Pandas-based data handler 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"}]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
resolution-markers = [
|
|
5
5
|
"python_full_version >= '3.12'",
|
|
@@ -613,7 +613,7 @@ wheels = [
|
|
|
613
613
|
|
|
614
614
|
[[package]]
|
|
615
615
|
name = "pdmt5"
|
|
616
|
-
version = "0.
|
|
616
|
+
version = "0.2.0"
|
|
617
617
|
source = { editable = "." }
|
|
618
618
|
dependencies = [
|
|
619
619
|
{ name = "metatrader5", marker = "sys_platform == 'win32'" },
|
|
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
|