11"""Module for trading operations with MetaTrader 5.
22
33Provides 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.
413"""
514
615import logging
@@ -113,7 +122,8 @@ def select_symbol(self) -> None:
113122 Returns:
114123 None
115124 """
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
117127
118128 def prepare_symbol (self ) -> None :
119129 """Prepare the trading symbol for opening positions.
@@ -130,25 +140,60 @@ def prepare_symbol(self) -> None:
130140
131141 if not symbol_info .visible :
132142 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
134145 logger .error (
135146 f"The expert advisor { self .expert_name } failed in select the symbol { self .symbol } , turning off."
136147 )
137148 Mt5 .shutdown ()
138149 logger .error ("Turned off" )
139150 sys .exit (1 )
140151
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+
141182 def summary (self ) -> None :
142183 """Print a summary of the expert advisor parameters.
143184
144185 Returns:
145186 None
146187 """
188+ trade_mode = Mt5 .symbol_info (self .symbol ).trade_mode
189+ trade_mode_desc = self .get_trade_mode_description ()
190+
147191 logger .info (
148192 f"Summary:\n "
149193 f"ExpertAdvisor name: { self .expert_name } \n "
150194 f"ExpertAdvisor version: { self .version } \n "
151195 f"Running on symbol: { self .symbol } \n "
196+ f"Symbol trade mode: { trade_mode } - { trade_mode_desc } \n "
152197 f"MagicNumber: { self .magic_number } \n "
153198 f"Number of lot(s): { self .lot } \n "
154199 f"StopLoss: { self .stop_loss } \n "
@@ -185,6 +230,18 @@ def open_buy_position(self, comment: str = "") -> None:
185230 Returns:
186231 None
187232 """
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+
188245 point = Mt5 .symbol_info (self .symbol ).point
189246 price = Mt5 .symbol_info_tick (self .symbol ).ask
190247
@@ -217,6 +274,18 @@ def open_sell_position(self, comment: str = "") -> None:
217274 Returns:
218275 None
219276 """
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+
220289 point = Mt5 .symbol_info (self .symbol ).point
221290 price = Mt5 .symbol_info_tick (self .symbol ).bid
222291
@@ -261,6 +330,64 @@ def request_result(self, price: float, result: int) -> None:
261330 else :
262331 logger .info (f"Position Closed: { result .price } " )
263332
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+
264391 def open_position (self , * , should_buy : bool , should_sell : bool , comment : str = "" ) -> None :
265392 """Open a position based on buy and sell conditions.
266393
@@ -272,16 +399,22 @@ def open_position(self, *, should_buy: bool, should_sell: bool, comment: str = "
272399 Returns:
273400 None
274401 """
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
275409 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+ )
282413
414+ # Check for stop loss and take profit conditions
283415 self .stop_and_gain (comment )
284416
417+ # Check if it's the end of the trading day
285418 if self .days_end ():
286419 logger .info ("It is the end of trading the day." )
287420 logger .info ("Closing all positions." )
@@ -297,6 +430,13 @@ def close_position(self, comment: str = "") -> None:
297430 Returns:
298431 None
299432 """
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+
300440 if len (Mt5 .positions_get (symbol = self .symbol )) == 1 :
301441 if Mt5 .positions_get (symbol = self .symbol )[0 ].type == 0 : # Buy position
302442 self .open_sell_position (comment )
0 commit comments