1
1
"""Module for trading operations with MetaTrader 5.
2
2
3
3
Provides a Trade class for managing trading operations.
4
+
5
+ Trade Modes:
6
+ - 0: Disabled - Trading is completely disabled for the symbol
7
+ - 1: Long only - Only buy positions allowed
8
+ - 2: Short only - Only sell positions allowed
9
+ - 3: Long and Short - Both buy and sell positions allowed (regular trading)
10
+ - 4: Close only - Only position closing is allowed, no new positions can be opened
11
+
12
+ The Trade class automatically respects these limitations when attempting to open or close positions.
4
13
"""
5
14
6
15
import logging
@@ -113,7 +122,8 @@ def select_symbol(self) -> None:
113
122
Returns:
114
123
None
115
124
"""
116
- Mt5 .symbol_select (self .symbol , select = True )
125
+ # Using positional arguments as the MetaTrader5 library doesn't support keywords
126
+ Mt5 .symbol_select (self .symbol , True ) # noqa: FBT003
117
127
118
128
def prepare_symbol (self ) -> None :
119
129
"""Prepare the trading symbol for opening positions.
@@ -130,25 +140,60 @@ def prepare_symbol(self) -> None:
130
140
131
141
if not symbol_info .visible :
132
142
logger .warning (f"The { self .symbol } is not visible, needed to be switched on." )
133
- if not Mt5 .symbol_select (self .symbol , select = True ):
143
+ # Using positional arguments as the MetaTrader5 library doesn't support keywords
144
+ if not Mt5 .symbol_select (self .symbol , True ): # noqa: FBT003
134
145
logger .error (
135
146
f"The expert advisor { self .expert_name } failed in select the symbol { self .symbol } , turning off."
136
147
)
137
148
Mt5 .shutdown ()
138
149
logger .error ("Turned off" )
139
150
sys .exit (1 )
140
151
152
+ # Check the trade mode
153
+ if symbol_info .trade_mode == 0 :
154
+ logger .warning (
155
+ f"Trading is disabled for { self .symbol } (trade_mode = 0). No positions can be opened or closed."
156
+ )
157
+ elif symbol_info .trade_mode == 4 :
158
+ logger .warning (
159
+ f"{ self .symbol } is in 'Close only' mode (trade_mode = 4). Only existing positions can be closed."
160
+ )
161
+
162
+ def get_trade_mode_description (self ) -> str :
163
+ """Get a description of the symbol's trade mode.
164
+
165
+ Returns:
166
+ str: A description of the trade mode.
167
+ """
168
+ trade_mode = Mt5 .symbol_info (self .symbol ).trade_mode
169
+
170
+ if trade_mode == 0 :
171
+ return "Disabled (trading disabled for the symbol)"
172
+ if trade_mode == 1 :
173
+ return "Long only (only buy positions allowed)"
174
+ if trade_mode == 2 :
175
+ return "Short only (only sell positions allowed)"
176
+ if trade_mode == 3 :
177
+ return "Long and Short (both buy and sell positions allowed)"
178
+ if trade_mode == 4 :
179
+ return "Close only (only position closing is allowed)"
180
+ return f"Unknown trade mode: { trade_mode } "
181
+
141
182
def summary (self ) -> None :
142
183
"""Print a summary of the expert advisor parameters.
143
184
144
185
Returns:
145
186
None
146
187
"""
188
+ trade_mode = Mt5 .symbol_info (self .symbol ).trade_mode
189
+ trade_mode_desc = self .get_trade_mode_description ()
190
+
147
191
logger .info (
148
192
f"Summary:\n "
149
193
f"ExpertAdvisor name: { self .expert_name } \n "
150
194
f"ExpertAdvisor version: { self .version } \n "
151
195
f"Running on symbol: { self .symbol } \n "
196
+ f"Symbol trade mode: { trade_mode } - { trade_mode_desc } \n "
152
197
f"MagicNumber: { self .magic_number } \n "
153
198
f"Number of lot(s): { self .lot } \n "
154
199
f"StopLoss: { self .stop_loss } \n "
@@ -185,6 +230,18 @@ def open_buy_position(self, comment: str = "") -> None:
185
230
Returns:
186
231
None
187
232
"""
233
+ # Check trade mode to see if Buy operations are allowed
234
+ symbol_info = Mt5 .symbol_info (self .symbol )
235
+ if symbol_info .trade_mode == 0 :
236
+ logger .warning (f"Cannot open Buy position for { self .symbol } - trading is disabled." )
237
+ return
238
+ if symbol_info .trade_mode == 2 : # Short only
239
+ logger .warning (f"Cannot open Buy position for { self .symbol } - only Sell positions are allowed." )
240
+ return
241
+ if symbol_info .trade_mode == 4 and len (Mt5 .positions_get (symbol = self .symbol )) == 0 :
242
+ logger .warning (f"Cannot open Buy position for { self .symbol } - symbol is in 'Close only' mode." )
243
+ return
244
+
188
245
point = Mt5 .symbol_info (self .symbol ).point
189
246
price = Mt5 .symbol_info_tick (self .symbol ).ask
190
247
@@ -217,6 +274,18 @@ def open_sell_position(self, comment: str = "") -> None:
217
274
Returns:
218
275
None
219
276
"""
277
+ # Check trade mode to see if Sell operations are allowed
278
+ symbol_info = Mt5 .symbol_info (self .symbol )
279
+ if symbol_info .trade_mode == 0 :
280
+ logger .warning (f"Cannot open Sell position for { self .symbol } - trading is disabled." )
281
+ return
282
+ if symbol_info .trade_mode == 1 : # Long only
283
+ logger .warning (f"Cannot open Sell position for { self .symbol } - only Buy positions are allowed." )
284
+ return
285
+ if symbol_info .trade_mode == 4 and len (Mt5 .positions_get (symbol = self .symbol )) == 0 :
286
+ logger .warning (f"Cannot open Sell position for { self .symbol } - symbol is in 'Close only' mode." )
287
+ return
288
+
220
289
point = Mt5 .symbol_info (self .symbol ).point
221
290
price = Mt5 .symbol_info_tick (self .symbol ).bid
222
291
@@ -261,6 +330,64 @@ def request_result(self, price: float, result: int) -> None:
261
330
else :
262
331
logger .info (f"Position Closed: { result .price } " )
263
332
333
+ def _handle_trade_mode_restrictions (self , symbol_info : Mt5 .SymbolInfo ) -> bool :
334
+ """Handle trade mode restrictions for different symbol types.
335
+
336
+ Args:
337
+ symbol_info (Mt5.SymbolInfo): The symbol information.
338
+
339
+ Returns:
340
+ bool: True if a position was opened or a restriction was handled, False otherwise.
341
+ """
342
+ # Check if the symbol is in "Disabled" mode (trade_mode = 0)
343
+ if symbol_info .trade_mode == 0 :
344
+ logger .warning (f"Cannot open new positions for { self .symbol } - trading is disabled." )
345
+ return True
346
+
347
+ # Check if the symbol is in "Close only" mode (trade_mode = 4)
348
+ if symbol_info .trade_mode == 4 and len (Mt5 .positions_get (symbol = self .symbol )) == 0 :
349
+ logger .warning (f"Cannot open new positions for { self .symbol } - symbol is in 'Close only' mode." )
350
+ return True
351
+
352
+ # No restrictions that prevent all trading
353
+ return False
354
+
355
+ def _handle_position_by_trade_mode (
356
+ self , symbol_info : Mt5 .SymbolInfo , * , should_buy : bool , should_sell : bool , comment : str
357
+ ) -> None :
358
+ """Open a position based on trade mode and buy/sell conditions.
359
+
360
+ Args:
361
+ symbol_info (Mt5.SymbolInfo): The symbol information.
362
+ should_buy (bool): Whether a buy position should be opened.
363
+ should_sell (bool): Whether a sell position should be opened.
364
+ comment (str): A comment for the trade.
365
+ """
366
+ # For "Long only" mode (trade_mode = 1), only allow Buy positions
367
+ if symbol_info .trade_mode == 1 :
368
+ if should_buy :
369
+ self .open_buy_position (comment )
370
+ self .total_deals += 1
371
+ elif should_sell :
372
+ logger .warning (f"Cannot open Sell position for { self .symbol } - only Buy positions are allowed." )
373
+
374
+ # For "Short only" mode (trade_mode = 2), only allow Sell positions
375
+ elif symbol_info .trade_mode == 2 :
376
+ if should_sell :
377
+ self .open_sell_position (comment )
378
+ self .total_deals += 1
379
+ elif should_buy :
380
+ logger .warning (f"Cannot open Buy position for { self .symbol } - only Sell positions are allowed." )
381
+
382
+ # For regular trading (trade_mode = 3) or other modes, allow both Buy and Sell
383
+ else :
384
+ if should_buy and not should_sell :
385
+ self .open_buy_position (comment )
386
+ self .total_deals += 1
387
+ if should_sell and not should_buy :
388
+ self .open_sell_position (comment )
389
+ self .total_deals += 1
390
+
264
391
def open_position (self , * , should_buy : bool , should_sell : bool , comment : str = "" ) -> None :
265
392
"""Open a position based on buy and sell conditions.
266
393
@@ -272,16 +399,22 @@ def open_position(self, *, should_buy: bool, should_sell: bool, comment: str = "
272
399
Returns:
273
400
None
274
401
"""
402
+ symbol_info = Mt5 .symbol_info (self .symbol )
403
+
404
+ # Check trade mode restrictions
405
+ if self ._handle_trade_mode_restrictions (symbol_info ):
406
+ return
407
+
408
+ # Open a position if no existing positions and within trading time
275
409
if (len (Mt5 .positions_get (symbol = self .symbol )) == 0 ) and self .trading_time ():
276
- if should_buy and not should_sell :
277
- self .open_buy_position (comment )
278
- self .total_deals += 1
279
- if should_sell and not should_buy :
280
- self .open_sell_position (comment )
281
- self .total_deals += 1
410
+ self ._handle_position_by_trade_mode (
411
+ symbol_info , should_buy = should_buy , should_sell = should_sell , comment = comment
412
+ )
282
413
414
+ # Check for stop loss and take profit conditions
283
415
self .stop_and_gain (comment )
284
416
417
+ # Check if it's the end of the trading day
285
418
if self .days_end ():
286
419
logger .info ("It is the end of trading the day." )
287
420
logger .info ("Closing all positions." )
@@ -297,6 +430,13 @@ def close_position(self, comment: str = "") -> None:
297
430
Returns:
298
431
None
299
432
"""
433
+ symbol_info = Mt5 .symbol_info (self .symbol )
434
+
435
+ # If trading is completely disabled for the symbol, log a warning and return
436
+ if symbol_info .trade_mode == 0 :
437
+ logger .warning (f"Cannot close position for { self .symbol } - trading is disabled for this symbol." )
438
+ return
439
+
300
440
if len (Mt5 .positions_get (symbol = self .symbol )) == 1 :
301
441
if Mt5 .positions_get (symbol = self .symbol )[0 ].type == 0 : # Buy position
302
442
self .open_sell_position (comment )
0 commit comments