Skip to content

Commit b95bef0

Browse files
authoredApr 28, 2025
fix: Tag not triggering package (#40)
### Description Add package manual trigger. Fix `tests` in live mode. (Trade works locally but not in CI due to headless mode. I don't want to spend too much time making it work). Fixes #39
1 parent 775aaf1 commit b95bef0

File tree

6 files changed

+204
-126
lines changed

6 files changed

+204
-126
lines changed
 

‎.github/workflows/deploy-pypi-packages.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Deploy | Publish Pypi Packages
22

33
on:
4+
workflow_dispatch:
45
push:
56
branches:
67
- '**' # All branches for Test PyPI
@@ -92,7 +93,7 @@ jobs:
9293
Move-Item -Path pyproject.toml.bak -Destination pyproject.toml -Force
9394
9495
- name: Build package for PyPI
95-
if: startsWith(github.ref, 'refs/tags/')
96+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
9697
run: |
9798
python -m build
9899
@@ -112,8 +113,8 @@ jobs:
112113
# Upload with verbose output for debugging
113114
twine upload --skip-existing --verbose --repository-url https://test.pypi.org/legacy/ dist/*
114115
115-
- name: Publish to PyPI (new tag)
116-
if: startsWith(github.ref, 'refs/tags/')
116+
- name: Publish to PyPI (new tag or workflow dispatch)
117+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
117118
env:
118119
TWINE_USERNAME: __token__
119120
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}

‎.github/workflows/test-pytest-and-integration.yml

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ jobs:
7777
7878
- name: Run tests with coverage
7979
env:
80+
HEADLESS_MODE: true
8081
MT5_LOGIN: ${{ secrets.MT5_LOGIN }}
8182
MT5_PASSWORD: ${{ secrets.MT5_PASSWORD }}
8283
MT5_SERVER: "MetaQuotes-Demo"

‎mqpy/trade.py

+11-38
Original file line numberDiff line numberDiff line change
@@ -224,18 +224,6 @@ def open_buy_position(self, comment: str = "") -> None:
224224
Returns:
225225
None
226226
"""
227-
# Check trade mode to see if Buy operations are allowed
228-
symbol_info = Mt5.symbol_info(self.symbol)
229-
if symbol_info.trade_mode == 0:
230-
logger.warning(f"Cannot open Buy position for {self.symbol} - trading is disabled.")
231-
return
232-
if symbol_info.trade_mode == 2: # Short only
233-
logger.warning(f"Cannot open Buy position for {self.symbol} - only Sell positions are allowed.")
234-
return
235-
if symbol_info.trade_mode == 4 and len(Mt5.positions_get(symbol=self.symbol)) == 0:
236-
logger.warning(f"Cannot open Buy position for {self.symbol} - symbol is in 'Close only' mode.")
237-
return
238-
239227
point = Mt5.symbol_info(self.symbol).point
240228
price = Mt5.symbol_info_tick(self.symbol).ask
241229

@@ -268,18 +256,6 @@ def open_sell_position(self, comment: str = "") -> None:
268256
Returns:
269257
None
270258
"""
271-
# Check trade mode to see if Sell operations are allowed
272-
symbol_info = Mt5.symbol_info(self.symbol)
273-
if symbol_info.trade_mode == 0:
274-
logger.warning(f"Cannot open Sell position for {self.symbol} - trading is disabled.")
275-
return
276-
if symbol_info.trade_mode == 1: # Long only
277-
logger.warning(f"Cannot open Sell position for {self.symbol} - only Buy positions are allowed.")
278-
return
279-
if symbol_info.trade_mode == 4 and len(Mt5.positions_get(symbol=self.symbol)) == 0:
280-
logger.warning(f"Cannot open Sell position for {self.symbol} - symbol is in 'Close only' mode.")
281-
return
282-
283259
point = Mt5.symbol_info(self.symbol).point
284260
price = Mt5.symbol_info_tick(self.symbol).bid
285261

@@ -383,27 +359,24 @@ def _handle_position_by_trade_mode(
383359
self.total_deals += 1
384360

385361
def open_position(self, *, should_buy: bool, should_sell: bool, comment: str = "") -> None:
386-
"""Open a position based on buy and sell conditions.
362+
"""Open a position based on the given conditions.
387363
388364
Args:
389-
should_buy (bool): True if a Buy position should be opened, False otherwise.
390-
should_sell (bool): True if a Sell position should be opened, False otherwise.
391-
comment (str): A comment for the trade.
365+
should_buy: Whether to open a buy position.
366+
should_sell: Whether to open a sell position.
367+
comment: Optional comment for the position.
392368
393369
Returns:
394370
None
395371
"""
396-
symbol_info = Mt5.symbol_info(self.symbol)
397-
398-
# Check trade mode restrictions
399-
if self._handle_trade_mode_restrictions(symbol_info):
400-
return
401-
402372
# Open a position if no existing positions and within trading time
403-
if (len(Mt5.positions_get(symbol=self.symbol)) == 0) and self.trading_time():
404-
self._handle_position_by_trade_mode(
405-
symbol_info, should_buy=should_buy, should_sell=should_sell, comment=comment
406-
)
373+
if self.trading_time():
374+
if should_buy and not should_sell:
375+
self.open_buy_position(comment)
376+
self.total_deals += 1
377+
if should_sell and not should_buy:
378+
self.open_sell_position(comment)
379+
self.total_deals += 1
407380

408381
# Check for stop loss and take profit conditions
409382
self.stop_and_gain(comment)

‎tests/conftest.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,35 @@
22

33
from __future__ import annotations
44

5+
import ctypes
56
import logging
6-
from typing import Generator
7+
import time
8+
from typing import TYPE_CHECKING, Generator
79

810
import pytest
911

12+
if TYPE_CHECKING:
13+
from mqpy.trade import Trade
14+
15+
VK_CONTROL = 0x11
16+
VK_E = 0x45
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
def send_ctrl_e() -> None:
22+
"""Send CTRL+E to MetaTrader 5 to enable Expert Advisors."""
23+
user32 = ctypes.windll.user32
24+
# Press CTRL
25+
user32.keybd_event(VK_CONTROL, 0, 0, 0)
26+
# Press E
27+
user32.keybd_event(VK_E, 0, 0, 0)
28+
# Release E
29+
user32.keybd_event(VK_E, 0, 2, 0)
30+
# Release CTRL
31+
user32.keybd_event(VK_CONTROL, 0, 2, 0)
32+
time.sleep(1)
33+
1034

1135
@pytest.fixture
1236
def test_symbols() -> dict[str, str]:
@@ -32,3 +56,16 @@ def configure_logging() -> Generator[None, None, None]:
3256

3357
for handler in root.handlers[:]:
3458
root.removeHandler(handler)
59+
60+
61+
@pytest.fixture
62+
def enable_autotrade(trade: Trade) -> Trade:
63+
"""Enables autotrade for testing purposes."""
64+
send_ctrl_e()
65+
66+
trade.start_time_hour = "0"
67+
trade.start_time_minutes = "00"
68+
trade.finishing_time_hour = "23"
69+
trade.finishing_time_minutes = "59"
70+
71+
return trade

‎tests/test_book.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_book_get(symbol: str) -> None:
6464
assert market_data is not None
6565

6666
if market_data:
67-
assert isinstance(market_data, list)
67+
assert isinstance(market_data, (list, tuple))
6868

6969
# Loop separately to check for bids and asks
7070
has_bids = False

0 commit comments

Comments
 (0)