Your AsimovMT class provides a comprehensive interface for interacting with MetaTrader5 (MT5) using Python. However, there are several areas in your code that could benefit from improvements, corrections, and enhancements to ensure robustness and maintainability. Below is a detailed review with suggestions:
Issue:
In the __init__ method, if mt5.initialize() fails, you print an error message and call mt5.shutdown(), but the program continues to execute. This can lead to unexpected behavior since subsequent operations depend on a successful initialization.
Recommendation:
After shutting down MT5, you should raise an exception or exit the program to prevent further execution.
Code Correction:
def __init__(self): if not mt5.initialize(): print("initialize() failed, error code =", mt5.last_error()) mt5.shutdown() raise ConnectionError("Failed to initialize MetaTrader5.") # Rest of your initialization code...
Issue:
There is a consistent typo in the attribute name self.positons. It should be self.positions.
Impact:
This typo will lead to AttributeError when accessing or modifying self.positions elsewhere in the class.
Code Correction:
# Corrected attribute name self.positions = [i._asdict() for i in mt5.positions_get()]
Ensure that all instances of self.positons are corrected to self.positions throughout the class, including methods like check_positions_and_orders.
Issue:
In the __main__ block, you use self to refer to the instance of AsimovMT. Typically, self is reserved for instance methods within a class.
Recommendation:
Use a different variable name (e.g., asimov_mt) to avoid confusion.
Code Correction:
if __name__ == "__main__": # Instance asimov_mt = AsimovMT() # Testing methods using asimov_mt instead of self start_date = datetime.today() - timedelta(days=1) df_data = asimov_mt.get_ohlc_range('PETR4', 'M1', start_date, datetime.today()) df_data = asimov_mt.get_ohlc_pos('PETR4', 'M1', 0, 1000) # ... and so on
Issue:
In the send_market_order method, you adjust the price by adding or subtracting 5 * ticks for buy and sell orders, respectively. Market orders are typically executed at the current market price without such adjustments.
Recommendation:
Use the current ask price for buy orders and bid price for sell orders without manual adjustments. If you intend to place a limit order, ensure that it aligns with your strategy.
Code Correction:
def send_market_order(self, symbol, side, volume, deviation=20, magic=1000, comment='test'): mt5.symbol_select(symbol, True) symbol_info = mt5.symbol_info(symbol) if side.lower() == 'buy': price = symbol_info.ask order_type = mt5.ORDER_TYPE_BUY elif side.lower() == 'sell': price = symbol_info.bid order_type = mt5.ORDER_TYPE_SELL else: raise ValueError("Invalid side. Use 'buy' or 'sell'.") if symbol_info.visible: request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_type, "price": price, "deviation": deviation, "magic": magic, "comment": comment, "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_RETURN, } result = mt5.order_send(request) if result.retcode != mt5.TRADE_RETCODE_DONE: print(f"Order failed, retcode={result.retcode}") return result else: print(f"{symbol} not visible. Unable to send market order.")
Issue:
The current implementation uses print statements for error messages and notifications. This approach can be limiting for larger applications where logging levels and outputs need to be managed more granularly.
Recommendation:
Use Python’s built-in logging module to provide flexible and configurable logging.
Code Enhancement:
import logging # Configure logging at the beginning of your script logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Replace print statements with logging logging.info("MetaTrader5 loaded. Ready to start.") logging.error(f"initialize() failed, error code = {mt5.last_error()}")
Issue:
Methods like send_limit_order and update_limit_order assume that input parameters are valid without performing checks.
Recommendation:
Add validation for input parameters to ensure they meet expected formats and constraints.
Code Example:
def send_limit_order(self, symbol, side, price, volume, magic=1000, comment=""): if side.lower() not in ['buy', 'sell']: raise ValueError("Invalid side. Use 'buy' or 'sell'.") if price7. Enhancing the tf_dict
Issue:
Your tf_dict maps timeframe strings to their MT5 constants and their equivalent in seconds. The comment suggests uncertainty about how timeframes represent days.Recommendation:
Consider calculating the number of bars or days based on the timeframe and your specific use case. Additionally, you might want to include more descriptive information or helper methods to handle timeframe conversions.Code Enhancement:
from enum import Enum class TimeFrame(Enum): M1 = mt5.TIMEFRAME_M1 M2 = mt5.TIMEFRAME_M2 # ... other timeframes self.tf_dict = { 'M1': (TimeFrame.M1.value, 60), 'M2': (TimeFrame.M2.value, 120), # ... other mappings }This approach makes the timeframes more manageable and less error-prone.
8. Handling copy_rates_range and copy_rates_from_pos Responses
Issue:
In methods get_ohlc_range and get_ohlc_pos, if data_raw is empty, the method returns None implicitly. This can lead to unexpected NoneType errors downstream.Recommendation:
Explicitly handle empty responses by returning an empty DataFrame or raising an exception, depending on your use case.Code Correction:
def get_ohlc_range(self, symbol, timeframe, start_date, end_date=datetime.now()): tf = self.tf_dict.get(timeframe, [None])[0] if tf is None: raise ValueError(f"Invalid timeframe: {timeframe}") data_raw = mt5.copy_rates_range(symbol, tf, start_date, end_date) if data_raw is None: logging.warning("No data retrieved for the given range.") return pd.DataFrame() df_data = self._format_ohlc(data_raw) return df_data if df_data is not None else pd.DataFrame()9. Improving the update_limit_order Method
Issue:
The update_limit_order method updates the order's price and volume but doesn't validate whether the order exists or whether the modification was successful.Recommendation:
Check if the order exists before attempting to modify it and handle the response from order_send appropriately.Code Enhancement:
def update_limit_order(self, order_id, price, volume): order = mt5.order_get(ticket=order_id) if order is None: logging.error(f"Order ID {order_id} not found.") return None request = { "action": mt5.TRADE_ACTION_MODIFY, "order": order_id, "price": price, "volume": volume, } result = mt5.order_send(request) if result.retcode != mt5.TRADE_RETCODE_DONE: logging.error(f"Failed to update order {order_id}, retcode={result.retcode}") return result._asdict()10. Ensuring Thread Safety
Issue:
If you plan to use AsimovMT in a multi-threaded environment, concurrent access to shared resources like self.positions and self.orders could lead to race conditions.Recommendation:
Implement thread synchronization mechanisms (e.g., threading locks) to protect shared resources.Code Example:
import threading class AsimovMT: def __init__(self): # Existing initialization code... self.lock = threading.Lock() def check_positions_and_orders(self): with self.lock: new_positions = [i._asdict() for i in mt5.positions_get()] new_orders = [i._asdict() for i in mt5.orders_get()] check = (self.positions != new_positions) or (self.orders != new_orders) self.positions = new_positions self.orders = new_orders return check11. Finalizing MT5 Connection
Issue:
There’s no mechanism to properly shut down the MT5 connection when the program exits, which can lead to resource leaks.Recommendation:
Implement a destructor method (__del__) or use context managers to ensure mt5.shutdown() is called appropriately.Code Example Using Destructor:
def __del__(self): mt5.shutdown() logging.info("MetaTrader5 connection closed.")Or Using Context Manager:
from contextlib import contextmanager @contextmanager def asimov_mt_context(): asimov_mt = AsimovMT() try: yield asimov_mt finally: del asimov_mt # Usage if __name__ == "__main__": with asimov_mt_context() as asimov_mt: # Your testing code here12. Enhancing Documentation and Readability
Issue:
The current code lacks docstrings and comments in English, which can hinder understanding and maintenance, especially for collaborators who may not speak Portuguese.Recommendation:
Add docstrings to classes and methods, and ensure comments are in a consistent language (preferably English for broader accessibility).Code Example:
class AsimovMT: """ AsimovMT is a class that interfaces with MetaTrader5 to manage trading operations, retrieve market data, and monitor positions and orders. """ def __init__(self): """ Initializes the MetaTrader5 connection and retrieves current positions, orders, and historical data. """ # Initialization code...13. Sample Revised Main Block
Here’s how your __main__ block could look after applying the recommendations:
if __name__ == "__main__": try: asimov_mt = AsimovMT() # Testing market data methods start_date = datetime.today() - timedelta(days=1) df_range = asimov_mt.get_ohlc_range('PETR4', 'M1', start_date, datetime.today()) print(df_range.head()) df_pos = asimov_mt.get_ohlc_pos('PETR4', 'M1', 0, 1000) print(df_pos.head()) # Testing balance methods positions_changed = asimov_mt.check_positions_and_orders() orders_changed = asimov_mt.check_positions_and_orders() print("Positions Changed:", positions_changed) print("Orders Changed:", orders_changed) h_positions_changed = asimov_mt.check_h_positions_and_orders(0) print("Historical Positions Changed:", h_positions_changed) print("Historical Deals:", asimov_mt.h_deals) print("Historical Orders:", asimov_mt.h_orders) # Testing order methods symbol = 'ITSA4' side = 'buy' volume = 100.0 # Should be float market_buy = asimov_mt.send_market_order(symbol, side, volume, comment='test') print("Market Buy Order:", market_buy) market_sell = asimov_mt.send_market_order(symbol, 'sell', volume, comment='test') print("Market Sell Order:", market_sell) limit_buy_price = 7.80 limit_buy = asimov_mt.send_limit_order(symbol, 'buy', limit_buy_price, volume, comment='test') print("Limit Buy Order:", limit_buy) limit_sell_price = 8.00 limit_sell = asimov_mt.send_limit_order(symbol, 'sell', limit_sell_price, volume, comment='test') print("Limit Sell Order:", limit_sell) # Update and cancel limit sell order if limit_sell: updated_order = asimov_mt.update_limit_order(limit_sell["order"], 8.01, volume=100.0) print("Updated Order:", updated_order) cancelled_order = asimov_mt.cancel_limit_order(limit_sell["order"]) print("Cancelled Order:", cancelled_order) except Exception as e: logging.exception("An error occurred during execution.")Conclusion
By addressing the issues and implementing the recommendations above, your AsimovMT class will become more robust, maintainable, and reliable. Proper error handling, input validation, and consistent naming conventions are crucial for developing scalable and professional-grade trading applications. Additionally, enhancing documentation and logging will aid in debugging and future development efforts.
Feel free to reach out if you have specific questions or need further assistance with particular aspects of your code!
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3