Error code 10016 [Invalid stops]

 
Could some review my code which I've attached below and let me know why do I occasionally get Error Code 10016 indicating it's an Invalid stops?

For reference here is one of the many log messages I receive in the MetaTrader 5's Journal
2023.06.02 00:23:36.885 Trades '5013799956': failed modify #50475513619 sell 0.01 AUDUSD sl: 0.65727, tp: 0.00000 -> sl: 0.65722, tp: 0.00000 [Invalid stops]

This seems to be an issue with the trail_sl function which handles the trailing of stop losses... but not exactly sure where to pin-point it...

I am using MetaQuotes-Demo server for testing out and ruling off what could be causing the issue for invalid stops... Perhaps the stop-loss is too small of a change for it to even send?
Documentation on MQL5: Constants, Enumerations and Structures / Codes of Errors and Warnings / Trade Server Return Codes
Documentation on MQL5: Constants, Enumerations and Structures / Codes of Errors and Warnings / Trade Server Return Codes
  • www.mql5.com
Trade Server Return Codes - Codes of Errors and Warnings - Constants, Enumerations and Structures - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5
Files:
mt5_test-bot.py  11 kb
 
Please don't create topics randomly in any section. It has been moved to the section: Expert Advisors and Automated Trading
MQL5 forum: Expert Advisors and Automated Trading
MQL5 forum: Expert Advisors and Automated Trading
  • www.mql5.com
How to create an Expert Advisor (a trading robot) for Forex trading
 

Are you verifying that the prices are aligned to the tick size? I don't see that in your code.

Are you verifying the Stops Level and Freeze Levels? I also don't see that in your code.

Articles

The checks a trading robot must pass before publication in the Market

MetaQuotes, 2016.08.01 09:30

Before any product is published in the Market, it must undergo compulsory preliminary checks in order to ensure a uniform quality standard. This article considers the most frequent errors made by developers in their technical indicators and trading robots. An also shows how to self-test a product before sending it to the Market.


Forum on trading, automated trading systems and testing trading strategies

Tick size vs Point(), can be a little tricky in Multicurrency EA

Fernando Carreiro, 2022.03.09 12:11

Tick Size and Point Size can be very different especially on stocks and other symbols besides forex.

Always use Tick Size to adjust and align your prices, not the point size. In essence, make sure that your price quotes, are properly aligned to the Tick size (see following examples).

...
double tickSize = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE );
...
double normalised_price = round( price / tick_size ) * tick_size;
...
// Or use a function
double Round2Ticksize( double price )
{
   double tick_size = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE );
   return( round( price / tick_size ) * tick_size );
};
 
Sorry for the late response!

Should I utilize tick size during trailing stop losses? Freeze Levels, Stop Levels are returning zero for now but thanks for pointing out as it'll be needed when placing orders and trailing stop losses as they change from broker to broker.

I've added in the necessary code snippet and added in a variable called tick_size which updates for every symbol, calculating and storing it under normalised_sl and just assigning that as my stop-loss send request, should I use it anywhere else for comparing? 
def trail_sl(symbol, order_ticket, timeframe, stop_loss_dict):
    while True:

        bars = mt5.copy_rates_from_pos(symbol, timeframe, 0, 4)
        bars_df = pd.DataFrame(bars)
        bar1_high = bars_df['high'].iloc[0]
        bar2_high = bars_df['high'].iloc[1]
        bar3_high = bars_df['high'].iloc[2]
        bar1_low = bars_df['low'].iloc[0]
        bar2_low = bars_df['low'].iloc[1]
        bar3_low = bars_df['low'].iloc[2]

        if mt5.positions_get(ticket=order_ticket):
            position_type = mt5.positions_get(ticket=order_ticket)[0].type
            if position_type == mt5.ORDER_TYPE_SELL:
                stop_loss_value = max(
                    bar1_high, bar2_high, bar3_high)  # Sell order S/L
            else:
                stop_loss_value = min(bar1_low, bar2_low,
                                      bar3_low)  # Buy order S/L

            tick_size = mt5.symbol_info(symbol).trade_tick_size
            normalised_sl = round(stop_loss_value / tick_size) * tick_size

            if normalised_sl != stop_loss_dict[symbol]:
                current_sl = mt5.positions_get(ticket=order_ticket)[0].sl
                if normalised_sl != current_sl:
                    request = {
                        "action": mt5.TRADE_ACTION_SLTP,
                        "position": order_ticket,
                        "symbol": symbol,
                        "magic": 24001,
                        "sl": normalised_sl
                    }
                    result = mt5.order_send(request)
                    if result.retcode == mt5.TRADE_RETCODE_DONE:
                        print(
                            f"[{datetime.now()}] Trailing Stop Loss for Order {order_ticket} updated. New S/L: {normalised_sl}")
                        print()
                        stop_loss_dict[symbol] = normalised_sl
                    elif result.retcode == 10025:  # Ignore error code 10025
                        pass
                    else:
                        print(
                            f"[{datetime.now()}] Failed to update Trailing Stop Loss for Order {order_ticket}: {result.comment}")
                        print(f"Error code: {result.retcode}")
                        print()

        time.sleep(1)  # Wait for 1 second before checking again