"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Comercio automatizado con MetaTraderGestión de pedidos y recopilación de datos de mercado

Comercio automatizado con MetaTraderGestión de pedidos y recopilación de datos de mercado

Publicado el 2024-11-08
Navegar:603

Automated Trading with MetaTraderOrder Management and Market Data Collection

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:


1. Initialization and Error Handling

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...

2. Typographical Errors

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.


3. Use of self in the Main Block

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

4. Market Order Price Adjustment

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.")

5. Consistent Error Handling and Logging

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()}")

6. Validating Input Parameters

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 price 





7. 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 check

11. 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 here

12. 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!

Declaración de liberación Este artículo se reproduce en: https://dev.to/vital7388/automated-trading-with-metatrader5-order-management-and-market-data-collection-4pb8?1 Si hay alguna infracción, comuníquese con Study_golang@163 .com para eliminarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3