├── requirements.txt ├── .gitattributes ├── LICENSE ├── .gitignore ├── utils.py ├── README.md └── MT5_Connector.py /requirements.txt: -------------------------------------------------------------------------------- 1 | MetaTrader5>=5.0.0 2 | pandas>=1.3.0 3 | numpy>=1.21.0 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 abdlhannan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | db.sqlite3-journal 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | Pipfile.lock 87 | 88 | # PEP 582 89 | __pypackages__/ 90 | 91 | # Celery stuff 92 | celerybeat-schedule 93 | celerybeat.pid 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # IDEs 126 | .vscode/ 127 | .idea/ 128 | *.swp 129 | *.swo 130 | *~ 131 | 132 | # OS specific 133 | .DS_Store 134 | Thumbs.db 135 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for MetaTrader 5 data handling. 3 | """ 4 | from MetaTrader5 import ( 5 | initialize, 6 | copy_rates_from_pos, 7 | TIMEFRAME_M1, 8 | TIMEFRAME_M5, 9 | TIMEFRAME_M15, 10 | TIMEFRAME_M30, 11 | TIMEFRAME_H1, 12 | TIMEFRAME_H4, 13 | TIMEFRAME_D1, 14 | ) 15 | import pandas as pd 16 | import numpy as np 17 | 18 | 19 | # Mapping of timeframe strings to MT5 constants 20 | TIMEFRAME_MAP = { 21 | 'M1': TIMEFRAME_M1, 22 | 'M5': TIMEFRAME_M5, 23 | 'M15': TIMEFRAME_M15, 24 | 'M30': TIMEFRAME_M30, 25 | 'H1': TIMEFRAME_H1, 26 | 'H4': TIMEFRAME_H4, 27 | 'D1': TIMEFRAME_D1, 28 | } 29 | 30 | 31 | def return_df(rates_array): 32 | """ 33 | Convert MT5 rates array to pandas DataFrame. 34 | 35 | Args: 36 | rates_array: Array of rates from MT5 containing OHLCV data 37 | 38 | Returns: 39 | pandas.DataFrame: DataFrame with columns for date_time, OHLC, volume, and spread data 40 | """ 41 | date_time = [x[0] for x in rates_array] 42 | open_price = [x[1] for x in rates_array] 43 | high = [x[2] for x in rates_array] 44 | low = [x[3] for x in rates_array] 45 | close = [x[4] for x in rates_array] 46 | tick_volume = [x[5] for x in rates_array] 47 | spread = [x[6] for x in rates_array] 48 | real_volume = [x[7] for x in rates_array] 49 | 50 | date_time = pd.to_datetime(date_time, unit='s') 51 | df = pd.DataFrame( 52 | np.transpose(np.array([date_time, open_price, high, low, close, tick_volume, spread, real_volume])), 53 | columns=['date_time', 'open', 'high', 'low', 'close', 'tick_volume', 'spread', 'real_volume'] 54 | ) 55 | return df 56 | 57 | 58 | def MT5_DATAGENERATOR_v2(pair, time_frame, win): 59 | """ 60 | Generate historical price data from MT5 for a given pair and timeframe. 61 | 62 | Args: 63 | pair: Trading pair symbol (e.g., 'EURUSD') 64 | time_frame: Timeframe string ('M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1') 65 | win: Number of bars to retrieve 66 | 67 | Returns: 68 | pandas.DataFrame: DataFrame containing OHLCV data 69 | 70 | Raises: 71 | ValueError: If timeframe is not supported 72 | """ 73 | initialize() 74 | 75 | if time_frame not in TIMEFRAME_MAP: 76 | raise ValueError(f'Unsupported timeframe: {time_frame}. Supported timeframes: {list(TIMEFRAME_MAP.keys())}') 77 | 78 | rates_array = copy_rates_from_pos(pair, TIMEFRAME_MAP[time_frame], 0, win) 79 | return return_df(rates_array) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MT5 Trade Connector 2 | 3 | A Python-based automated trading connector for MetaTrader 5 (MT5) platform with trailing stop functionality and risk management features. 4 | 5 | ## Features 6 | 7 | - **Automated Trading**: Execute market orders (buy/sell/close) programmatically 8 | - **Trailing Stop**: Automatic trailing stop loss adjustment based on price movements 9 | - **Risk Management**: Built-in daily and monthly loss limit controls 10 | - **Position Management**: Prevent multiple positions per strategy and auto-close opposite positions 11 | - **Multi-Timeframe Support**: Works with M1, M5, M15, M30, H1, H4, and D1 timeframes 12 | - **Thread-Safe**: Uses threading for concurrent operations 13 | 14 | ## Requirements 15 | 16 | - Python 3.6+ 17 | - MetaTrader 5 terminal installed 18 | - MT5 trading account 19 | 20 | ## Installation 21 | 22 | 1. Clone this repository: 23 | ```bash 24 | git clone https://github.com/abdlhannan/MT5_Trade_Connector.git 25 | cd MT5_Trade_Connector 26 | ``` 27 | 28 | 2. Install required packages: 29 | ```bash 30 | pip install -r requirements.txt 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### Basic Trading Example 36 | 37 | ```python 38 | from MT5_Connector import MT5_TRADECONNECTOR 39 | 40 | # Initialize the connector 41 | connector = MT5_TRADECONNECTOR( 42 | login=12345678, 43 | password="your_password", 44 | strategy_name="MyStrategy", 45 | TimeFrame="M15", 46 | maxposition=3, 47 | PATH="", # Path to MT5 terminal (optional) 48 | TrailingStopOn=True 49 | ) 50 | 51 | # Execute a long position 52 | connector.marketorder_trade_execution( 53 | PAIR="EURUSD", 54 | lot_size=0.1, 55 | TP=50, # Take profit in pips 56 | SL=30, # Stop loss in pips 57 | POSITION='LONG' 58 | ) 59 | 60 | # Execute a short position 61 | connector.marketorder_trade_execution( 62 | PAIR="GBPUSD", 63 | lot_size=0.1, 64 | TP=50, 65 | SL=30, 66 | POSITION='SHORT' 67 | ) 68 | 69 | # Close a position 70 | connector.marketorder_trade_execution( 71 | PAIR="EURUSD", 72 | lot_size=0.1, 73 | TP=0, 74 | SL=0, 75 | POSITION='CLOSE' 76 | ) 77 | ``` 78 | 79 | ### Risk Management Example 80 | 81 | ```python 82 | from MT5_Connector import RiskManagement_v1 83 | 84 | # Initialize risk management 85 | risk_mgmt = RiskManagement_v1( 86 | login=12345678, 87 | password="your_password", 88 | PATH="", 89 | daily_limit=0.05, # 5% daily loss limit 90 | monthly_limit=0.10 # 10% monthly loss limit 91 | ) 92 | 93 | # Check daily loss limits 94 | risk_mgmt.daily_losslimit_check() 95 | ``` 96 | 97 | ### Data Retrieval Example 98 | 99 | ```python 100 | from utils import MT5_DATAGENERATOR_v2 101 | 102 | # Get historical data 103 | df = MT5_DATAGENERATOR_v2( 104 | pair="EURUSD", 105 | time_frame="H1", 106 | win=100 # Number of bars 107 | ) 108 | 109 | print(df.head()) 110 | ``` 111 | 112 | ## Key Components 113 | 114 | ### MT5_TRADECONNECTOR 115 | 116 | Main trading class that handles order execution and position management. 117 | 118 | **Parameters:** 119 | - `login`: MT5 account login ID 120 | - `password`: MT5 account password 121 | - `strategy_name`: Name of your trading strategy (max 3 words) 122 | - `TimeFrame`: Timeframe ('M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1') 123 | - `maxposition`: Maximum number of positions allowed per strategy 124 | - `PATH`: Path to MT5 terminal (optional) 125 | - `TrailingStopOn`: Enable trailing stop functionality (default: False) 126 | 127 | **Key Methods:** 128 | - `marketorder_trade_execution()`: Execute market orders 129 | - `avoid_multiple_positions()`: Control position limits 130 | - `change_stoploss()`: Update trailing stop loss 131 | 132 | ### RiskManagement_v1 133 | 134 | Risk management class with loss limit controls. 135 | 136 | **Parameters:** 137 | - `login`: MT5 account login ID 138 | - `password`: MT5 account password 139 | - `PATH`: Path to MT5 terminal 140 | - `daily_limit`: Daily loss limit as percentage (default: 0.05) 141 | - `monthly_limit`: Monthly loss limit as percentage (default: 0.10) 142 | 143 | ### MT5_DATAGENERATOR_v2 144 | 145 | Utility function to retrieve historical price data from MT5. 146 | 147 | **Parameters:** 148 | - `pair`: Trading pair symbol (e.g., 'EURUSD') 149 | - `time_frame`: Timeframe string 150 | - `win`: Number of bars to retrieve 151 | 152 | **Returns:** pandas DataFrame with OHLCV data 153 | 154 | ## Supported Timeframes 155 | 156 | - M1: 1 minute 157 | - M5: 5 minutes 158 | - M15: 15 minutes 159 | - M30: 30 minutes 160 | - H1: 1 hour 161 | - H4: 4 hours 162 | - D1: 1 day 163 | 164 | ## Important Notes 165 | 166 | - **Strategy Name**: Should be maximum 3 words 167 | - **Trailing Stop**: When enabled, the connector automatically adjusts stop loss based on favorable price movements 168 | - **Position Management**: The system prevents opening more than `maxposition` positions per strategy and automatically closes opposite positions 169 | - **Thread Safety**: Trailing stop functionality runs in separate threads 170 | 171 | ## Error Handling 172 | 173 | The connector includes comprehensive error handling: 174 | - Connection failures are logged with error codes 175 | - Failed orders display detailed error information 176 | - Invalid parameters raise appropriate exceptions 177 | 178 | ## License 179 | 180 | [Add your license here] 181 | 182 | ## Contributing 183 | 184 | Contributions are welcome! Please feel free to submit a Pull Request. 185 | 186 | ## Disclaimer 187 | 188 | **Trading financial instruments carries risk. This software is provided for educational purposes only. Use at your own risk. The authors are not responsible for any financial losses incurred while using this software.** 189 | 190 | ## Support 191 | 192 | For issues and questions, please open an issue on GitHub. 193 | 194 | ## Author 195 | 196 | [Your Name/Username] 197 | -------------------------------------------------------------------------------- /MT5_Connector.py: -------------------------------------------------------------------------------- 1 | """ 2 | MetaTrader 5 Trade Connector module for automated trading. 3 | """ 4 | import time 5 | import threading 6 | import datetime 7 | from threading import Timer 8 | 9 | import MetaTrader5 as mt5 10 | import numpy as np 11 | import pandas as pd 12 | 13 | from utils import MT5_DATAGENERATOR_v2 14 | 15 | class Periodic_Timer_Thread(object): 16 | """ 17 | A periodic timer that runs a function at specified intervals using threading. 18 | """ 19 | 20 | def __init__(self, interval, function, comment='', *args, **kwargs): 21 | """ 22 | Initialize the periodic timer. 23 | 24 | Args: 25 | interval: Time interval in seconds between function calls 26 | function: Function to call periodically 27 | comment: Optional comment/name for the thread 28 | *args: Positional arguments to pass to the function 29 | **kwargs: Keyword arguments to pass to the function 30 | """ 31 | self._timer = None 32 | self.interval = interval 33 | self.function = function 34 | self.args = args 35 | self.kwargs = kwargs 36 | self.comment = comment 37 | self.is_running = False 38 | self.start() 39 | 40 | def _run(self): 41 | """Internal method to run the function and restart the timer.""" 42 | self.is_running = False 43 | self.start() 44 | self.function(*self.args, **self.kwargs) 45 | 46 | def start(self): 47 | """Start the periodic timer.""" 48 | if not self.is_running: 49 | self._timer = Timer(self.interval, self._run) 50 | self._timer.setName(self.comment) 51 | self._timer.start() 52 | self.is_running = True 53 | 54 | def stop(self): 55 | """Stop the periodic timer.""" 56 | self._timer.cancel() 57 | self.is_running = False 58 | 59 | 60 | 61 | class RiskManagement_v1(object): 62 | """ 63 | Risk management class for MT5 trading with daily and monthly loss limits. 64 | """ 65 | 66 | def __init__(self, login, password, PATH, daily_limit=0.05, monthly_limit=0.10): 67 | """ 68 | Initialize risk management with MT5 credentials. 69 | 70 | Args: 71 | login: MT5 account login ID 72 | password: MT5 account password 73 | PATH: Path to MT5 terminal 74 | daily_limit: Daily loss limit as percentage (default: 0.05 = 5%) 75 | monthly_limit: Monthly loss limit as percentage (default: 0.10 = 10%) 76 | """ 77 | self.login_id = login 78 | self.password = password 79 | self.PATH = PATH 80 | self.daily_limit = daily_limit 81 | self.monthly_limit = monthly_limit 82 | self.login() 83 | 84 | def login(self): 85 | """Establish connection to MetaTrader 5 terminal.""" 86 | print("MetaTrader5 package author: ", mt5.__author__) 87 | print("MetaTrader5 package version: ", mt5.__version__) 88 | 89 | if not mt5.initialize(self.PATH): 90 | print("initialize() failed, error code =", mt5.last_error()) 91 | quit() 92 | 93 | authorized = mt5.login(self.login_id, password=self.password) 94 | if not authorized: 95 | print('LOGIN FAILED!!!') 96 | mt5.shutdown() 97 | quit() 98 | else: 99 | print("Login with account: ", str(self.login_id), " successful!") 100 | 101 | def daily_losslimit_check(self): 102 | """Check daily loss limit against trading history.""" 103 | from_date = datetime.datetime(2020, 5, 27) 104 | to_date = datetime.datetime(2020, 5, 29) 105 | position_history_orders = mt5.history_orders_get(from_date, to_date) 106 | 107 | if position_history_orders: 108 | df = pd.DataFrame( 109 | list(position_history_orders), 110 | columns=position_history_orders[0]._asdict().keys() 111 | ) 112 | df['time_setup'] = pd.to_datetime(df['time_setup'], unit='s') 113 | df['time_done'] = pd.to_datetime(df['time_done'], unit='s') 114 | print(df[['time_done', 'time_setup', 'symbol']]) 115 | 116 | 117 | 118 | class MT5_TRADECONNECTOR(object): 119 | """ 120 | Main trading connector class for automated MT5 trading with trailing stop support. 121 | """ 122 | 123 | def __init__(self, login, password, strategy_name, TimeFrame, maxposition, 124 | PATH="", TrailingStopOn=False): 125 | """ 126 | Initialize the MT5 trade connector. 127 | 128 | Args: 129 | login: MT5 account login ID 130 | password: MT5 account password 131 | strategy_name: Name of the trading strategy (max 3 words) 132 | TimeFrame: Timeframe string ('M1', 'M15', 'M30', 'H1', 'H4', 'D1') 133 | maxposition: Maximum number of positions allowed per strategy 134 | PATH: Path to MT5 terminal (optional) 135 | TrailingStopOn: Enable trailing stop functionality (default: False) 136 | 137 | Note: 138 | Strategy_name should be max three words. 139 | Timeframe should be in the following format: M1, M15, M30, H1, H4, D1 140 | """ 141 | self.strategy_name = strategy_name 142 | self.TimeFrame = TimeFrame 143 | self.login_id = login 144 | self.password = password 145 | self.PATH = PATH 146 | self.threads = [] 147 | self.TrailingStopOn = TrailingStopOn 148 | self.maxposition = maxposition 149 | 150 | if self.TrailingStopOn: 151 | container_thread = threading.Thread(target=self.container_thread_routine) 152 | container_thread.start() 153 | 154 | def login(self): 155 | """Establish connection to MetaTrader 5 terminal.""" 156 | if not mt5.initialize(self.PATH): 157 | print("initialize() failed, error code =", mt5.last_error()) 158 | quit() 159 | 160 | authorized = mt5.login(self.login_id, password=self.password) 161 | if not authorized: 162 | print('LOGIN FAILED!!!') 163 | mt5.shutdown() 164 | quit() 165 | else: 166 | print("Login with account: ", str(self.login_id), " successful!") 167 | 168 | 169 | def container_thread_routine(self): 170 | """ 171 | Container routine for managing trailing stop threads based on timeframe. 172 | Calculates wait time and interval based on the current timeframe. 173 | """ 174 | now = datetime.datetime.now() 175 | 176 | if self.TimeFrame == 'M1': 177 | wait_second = 60 - now.second 178 | interval = 1 * 60 179 | time.sleep(wait_second) 180 | elif self.TimeFrame == 'M15': 181 | wait_minute = np.ceil(now.minute / 15) * 15 - now.minute 182 | interval = 15 * 60 183 | wait_second = wait_minute * 60 184 | time.sleep(wait_second) 185 | elif self.TimeFrame == 'M30': 186 | wait_minute = np.ceil(now.minute / 30) * 30 - now.minute 187 | interval = 30 * 60 188 | wait_second = wait_minute * 60 189 | time.sleep(wait_second) 190 | else: 191 | raise ValueError('Error in TimeFrame type...Not Supported!!') 192 | 193 | initial_thread = threading.Thread(target=self.change_stoploss) 194 | self.trailing_stop_thread = Periodic_Timer_Thread( 195 | interval=interval, 196 | function=self.change_stoploss, 197 | comment='trailing_stop' 198 | ) 199 | initial_thread.start() 200 | def change_stoploss(self): 201 | """Check and update stop loss for positions with trailing stop enabled.""" 202 | self.login() 203 | positions = mt5.positions_get() 204 | if positions is not None: 205 | for open_position in positions: 206 | if open_position.comment.find('TS') != -1: 207 | self.simplestoploss(open_position) 208 | 209 | def simplestoploss(self, open_position): 210 | """ 211 | Implement simple trailing stop logic for an open position. 212 | 213 | Args: 214 | open_position: MT5 position object to apply trailing stop to 215 | """ 216 | stoploss_limit = np.fromstring( 217 | open_position.comment[open_position.comment.find('TS') + 2] + 218 | open_position.comment[open_position.comment.find('TS') + 3], 219 | dtype=int, sep=' ' 220 | ) 221 | takeprofit_limit = np.fromstring( 222 | open_position.comment[open_position.comment.find('TP') + 2] + 223 | open_position.comment[open_position.comment.find('TP') + 3], 224 | dtype=int, sep=' ' 225 | ) 226 | order_type = open_position.type 227 | symbol = open_position.symbol 228 | current_price = open_position.price_current 229 | point = mt5.symbol_info(symbol).point 230 | order_timeframe = open_position.comment[open_position.comment.find(self.strategy_name) + 3:] 231 | 232 | past_data = MT5_DATAGENERATOR_v2(symbol, order_timeframe, 3) 233 | 234 | if order_type == 0: # LONG ORDER 235 | if (past_data['close'].iloc[1] > past_data['close'].iloc[0]) and open_position.profit > 0: 236 | new_sl = np.double(open_position.price_current - stoploss_limit * 10 * point) 237 | print(open_position.symbol + ': new sl:', new_sl, ' old SL: ', open_position.sl) 238 | 239 | if new_sl > open_position.sl: 240 | tp = np.double(open_position.price_open + takeprofit_limit * 10 * point) 241 | request = { 242 | "action": mt5.TRADE_ACTION_SLTP, 243 | "position": open_position.ticket, 244 | "symbol": open_position.symbol, 245 | "sl": new_sl, 246 | "tp": tp, 247 | "magic": 123456 248 | } 249 | result = mt5.order_send(request) 250 | if self.health_check(result._asdict()) == 'pass': 251 | print(open_position.symbol, ' SL Changed!!! Strategy: ', 252 | self.strategy_name, ' on TIMEFRAME: ', self.TimeFrame) 253 | 254 | elif order_type == 1: # SHORT ORDER 255 | if (past_data['close'].iloc[1] < past_data['close'].iloc[0]) and open_position.profit > 0: 256 | new_sl = np.double(open_position.price_current + stoploss_limit * 10 * point) 257 | print(open_position.symbol + ': new sl:', new_sl, ' old SL: ', open_position.sl) 258 | 259 | if new_sl < open_position.sl: 260 | tp = np.double(open_position.price_open - takeprofit_limit * 10 * point) 261 | request = { 262 | "action": mt5.TRADE_ACTION_SLTP, 263 | "position": open_position.ticket, 264 | "symbol": open_position.symbol, 265 | "sl": new_sl, 266 | "tp": tp, 267 | "magic": 123456 268 | } 269 | result = mt5.order_send(request) 270 | if self.health_check(result._asdict()) == 'pass': 271 | print(open_position.symbol, ' SL Changed!!! Strategy: ', 272 | self.strategy_name, ' on TIMEFRAME: ', self.TimeFrame) 273 | else: 274 | raise ValueError('Error in order_type') 275 | 276 | def avoid_multiple_positions(self, POSITION, PAIR): 277 | """ 278 | Restrict the bot to only have maxposition open positions of the same instrument per strategy. 279 | Also closes an existing position if strategy gives opposite signal. 280 | 281 | Args: 282 | POSITION: Position type ('LONG' or 'SHORT') 283 | PAIR: Trading pair symbol 284 | 285 | Returns: 286 | str: 'block' if max positions reached, 'noblock' otherwise 287 | """ 288 | if POSITION == 'LONG': 289 | position_type = 0 290 | elif POSITION == 'SHORT': 291 | position_type = 1 292 | 293 | self.login() 294 | positions = mt5.positions_get() 295 | position_count = 0 296 | 297 | for open_position in positions: 298 | if (open_position.comment.find(self.strategy_name) != -1 and 299 | open_position.comment.find(self.TimeFrame) != -1): 300 | if open_position.symbol == PAIR: 301 | if (open_position.type == 0 and position_type == 1): 302 | # CLOSE A LONG POSITION BY OPPOSITE ORDER 303 | request = { 304 | "action": mt5.TRADE_ACTION_DEAL, 305 | "symbol": open_position.symbol, 306 | "volume": open_position.volume, 307 | "type": mt5.ORDER_TYPE_SELL, 308 | "position": open_position.ticket, 309 | "price": mt5.symbol_info_tick(open_position.symbol).bid, 310 | "magic": open_position.magic, 311 | "comment": "close long pos", 312 | "type_time": mt5.ORDER_TIME_GTC, 313 | "type_filling": mt5.ORDER_FILLING_FOK, 314 | } 315 | result = mt5.order_send(request) 316 | if self.health_check(result._asdict()) == 'pass': 317 | print(open_position.symbol, ' with vol: ', open_position.volume, 318 | ' LONG POSITION CLOSED!!! Strategy: ', self.strategy_name, 319 | ' on TIMEFRAME: ', self.TimeFrame) 320 | 321 | elif (open_position.type == 1 and position_type == 0): 322 | # CLOSE A SHORT POSITION BY OPPOSITE ORDER 323 | request = { 324 | "action": mt5.TRADE_ACTION_DEAL, 325 | "symbol": open_position.symbol, 326 | "volume": open_position.volume, 327 | "type": mt5.ORDER_TYPE_BUY, 328 | "position": open_position.ticket, 329 | "price": mt5.symbol_info_tick(open_position.symbol).ask, 330 | "magic": open_position.magic, 331 | "comment": "close short pos", 332 | "type_time": mt5.ORDER_TIME_GTC, 333 | "type_filling": mt5.ORDER_FILLING_FOK, 334 | } 335 | result = mt5.order_send(request) 336 | if self.health_check(result._asdict()) == 'pass': 337 | print(open_position.symbol, ' with vol: ', open_position.volume, 338 | ' SHORT POSITION CLOSED!!! Strategy: ', self.strategy_name, 339 | ' on TIMEFRAME: ', self.TimeFrame) 340 | else: 341 | position_count = position_count + 1 342 | 343 | if position_count >= self.maxposition: 344 | action = 'block' 345 | else: 346 | action = 'noblock' 347 | return action 348 | def health_check(self, result): 349 | """ 350 | Check if the trade order was successful. 351 | 352 | Args: 353 | result: Dictionary containing trade result 354 | 355 | Returns: 356 | str: 'pass' if successful, 'notpass' otherwise 357 | """ 358 | if result['retcode'] != mt5.TRADE_RETCODE_DONE: 359 | print("2. order_send failed, retcode={}".format(result['retcode'])) 360 | # request the result as a dictionary and display it element by element 361 | for field in result.keys(): 362 | print(" {}={}".format(field, result[field])) 363 | # if this is a trading request structure, display it element by element as well 364 | if field == "request": 365 | traderequest_dict = result[field]._asdict() 366 | for tradereq_filed in traderequest_dict: 367 | print("traderequest: {}={}".format(tradereq_filed, traderequest_dict[tradereq_filed])) 368 | return 'notpass' 369 | else: 370 | return 'pass' 371 | def marketorder_trade_execution(self, PAIR, lot_size, TP, SL, POSITION): 372 | """ 373 | Execute a market order for buy, sell, or close positions. 374 | 375 | Args: 376 | PAIR: Trading pair symbol 377 | lot_size: Lot size for the order 378 | TP: Take profit in pips 379 | SL: Stop loss in pips 380 | POSITION: Position type ('LONG', 'SHORT', or 'CLOSE') 381 | 382 | MQL5 Order Types: 383 | ORDER_TYPE_BUY: Market Buy order 384 | ORDER_TYPE_SELL: Market Sell order 385 | ORDER_TYPE_BUY_LIMIT: Buy Limit pending order 386 | ORDER_TYPE_SELL_LIMIT: Sell Limit pending order 387 | ORDER_TYPE_BUY_STOP: Buy Stop pending order 388 | ORDER_TYPE_SELL_STOP: Sell Stop pending order 389 | ORDER_TYPE_BUY_STOP_LIMIT: Buy Limit at StopLimit price 390 | ORDER_TYPE_SELL_STOP_LIMIT: Sell Limit at StopLimit price 391 | ORDER_TYPE_CLOSE_BY: Order to close position by opposite one 392 | """ 393 | point = mt5.symbol_info(PAIR).point 394 | 395 | if POSITION == 'LONG': 396 | order_type = mt5.ORDER_TYPE_BUY 397 | price = mt5.symbol_info_tick(PAIR).ask 398 | sl = np.double(price - SL * 10 * point) 399 | tp = np.double(price + TP * 10 * point) 400 | 401 | if self.TrailingStopOn: 402 | comment = 'TP' + str(TP) + ' TS' + str(SL) + ' ' + self.strategy_name + self.TimeFrame 403 | else: 404 | comment = 'TP' + str(TP) + ' SL' + str(SL) + ' ' + self.strategy_name + self.TimeFrame 405 | 406 | deviation = 5 407 | request = { 408 | "action": mt5.TRADE_ACTION_DEAL, 409 | "symbol": PAIR, 410 | "volume": lot_size, 411 | "type": order_type, 412 | "price": price, 413 | "sl": np.double(sl), 414 | "tp": np.double(tp), 415 | "deviation": deviation, 416 | "magic": 234000, 417 | "comment": comment, 418 | "type_time": mt5.ORDER_TIME_GTC, 419 | "type_filling": mt5.ORDER_FILLING_FOK, 420 | } 421 | self.login() 422 | self.result = mt5.order_send(request) 423 | if self.health_check(self.result._asdict()) == 'pass': 424 | print("1. order_send(): by {} {} lots at {} with deviation={} points".format( 425 | PAIR, lot_size, price, deviation)) 426 | 427 | elif POSITION == 'SHORT': 428 | order_type = mt5.ORDER_TYPE_SELL 429 | price = mt5.symbol_info_tick(PAIR).bid 430 | sl = np.double(price + SL * 10 * point) 431 | tp = np.double(price - TP * 10 * point) 432 | 433 | if self.TrailingStopOn: 434 | comment = 'TP' + str(TP) + ' TS' + str(SL) + ' ' + self.strategy_name + self.TimeFrame 435 | else: 436 | comment = 'TP' + str(TP) + ' SL' + str(SL) + ' ' + self.strategy_name + self.TimeFrame 437 | 438 | deviation = 5 439 | request = { 440 | "action": mt5.TRADE_ACTION_DEAL, 441 | "symbol": PAIR, 442 | "volume": lot_size, 443 | "type": order_type, 444 | "price": price, 445 | "sl": np.double(sl), 446 | "tp": np.double(tp), 447 | "deviation": deviation, 448 | "magic": 234000, 449 | "comment": comment, 450 | "type_time": mt5.ORDER_TIME_GTC, 451 | "type_filling": mt5.ORDER_FILLING_FOK, 452 | } 453 | self.login() 454 | self.result = mt5.order_send(request) 455 | if self.health_check(self.result._asdict()) == 'pass': 456 | print("1. order_send(): by {} {} lots at {} with deviation={} points".format( 457 | PAIR, lot_size, price, deviation)) 458 | 459 | elif POSITION == 'CLOSE': 460 | self.login() 461 | positions = mt5.positions_get() 462 | if positions is not None: 463 | for open_position in positions: 464 | if (open_position.comment.find(self.strategy_name) != -1 and 465 | open_position.comment.find(self.TimeFrame) != -1 and 466 | PAIR == open_position.symbol): 467 | 468 | if open_position.order_type == 0: 469 | order_type = mt5.ORDER_TYPE_SELL 470 | price = mt5.symbol_info_tick(open_position.symbol).bid 471 | elif open_position.order_type == 1: 472 | order_type = mt5.ORDER_TYPE_BUY 473 | price = mt5.symbol_info_tick(open_position.symbol).ask 474 | 475 | comment = 'closed for ' + self.strategy_name + self.TimeFrame 476 | close_request = { 477 | "action": mt5.TRADE_ACTION_DEAL, 478 | "symbol": open_position.symbol, 479 | "volume": open_position.volume, 480 | "type": order_type, 481 | "position": open_position.ticket, 482 | "price": price, 483 | "deviation": 5, 484 | "magic": 0, 485 | "comment": comment, 486 | "type_time": mt5.ORDER_TIME_GTC, 487 | "type_filling": mt5.ORDER_FILLING_RETURN, 488 | } 489 | self.login() 490 | self.result = mt5.order_send(close_request) 491 | if self.health_check(self.result._asdict()) == 'pass': 492 | print("1. order_send(): by {} {} lots at {} with deviation={} points".format( 493 | PAIR, lot_size, price, 5)) 494 | else: 495 | return 496 | else: 497 | raise ValueError('POSITION TYPE IS INVALID FOR MARKET ORDERS!') 498 | --------------------------------------------------------------------------------