Avoid Trade Context Errors

Post Reply
magft
Trader
Posts: 195
Joined: Tue Nov 15, 2011 9:59 pm
Location: East Midlands, UK

Avoid Trade Context Errors

Post by magft »

George and I have been trying a semaphore method based on this article to stop Error 146 Trade Context Busy errors occuring when using multiple EAs on same Empty4 ie multiple pairs of timeframes.

I tried a few things for my ChangeTheColor EA but stil kept getting the odd one. George then suggested replacing calls to OrderSend and OrderModify with a customised version that uses a semaphore (Global Variable) that stops EAs sending trades until the semaphore says they can.

Just replace all calls to OrderSend with _OrderSend and OrderModify with _OrderModify and drop the following code into the EA.

Code: Select all

/////////////////////////////////////////////////////////////////////////
// 
// Replace internal functions with updated ones to use trade semaphores
//
/////////////////////////////////////////////////////////////////////////
int _OrderSend(string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment="", int magic=0, datetime expiration=0, color arrow_color=CLR_NONE) {

   int res = -1;
   
   //try to lock resource
   if (LockTradingThread()<0) {
      Alert("Unable to place trade, timeout exceeded.");
      return(res);
   }
   RefreshRates();

   //place trade
   res = OrderSend(symbol, cmd, volume, price, slippage, stoploss, takeprofit, comment, magic, expiration, arrow_color);
   
   //unlock resource
   UnlockTradingThread();
   return(res);
}

bool _OrderModify( int ticket, double price, double stoploss, double takeprofit, datetime expiration, color arrow_color=CLR_NONE) {

   bool res = false;
   
   //try to lock resource
   if (LockTradingThread()<0) {
      Alert("Unable to modify trade, timeout exceeded.");
      return(res);
   }
   RefreshRates();
   
   //modify order
   res = OrderModify(ticket, price, stoploss, takeprofit, expiration, arrow_color);
   
   //unlock resource
   UnlockTradingThread();
   return(res);
}

/////////////////////////////////////////////////////////////////////////////////
// int LockTradingThread( int MaxWaiting_sec = 30 )
//
// The function replaces the LockTradingThread value 0 with 1.
// If LockTradingThread = 1 at the moment of launch, the function waits until LockTradingThread is 0, 
// and then replaces.
// If there is no global variable LockTradingThread, the function creates it.
// Return codes:
//  1 - successfully completed. The global variable LockTradingThread was assigned with value 1
// -1 - LockTradingThread = 1 at the moment of launch of the function, the waiting was interrupted by the user
//      (the expert was removed from the chart, the terminal was closed, the chart period and/or symbol 
//      was changed, etc.)
// -2 - LockTradingThread = 1 at the moment of launch of the function, the waiting limit was exceeded
//      (MaxWaiting_sec)
/////////////////////////////////////////////////////////////////////////////////
int LockTradingThread( int MaxWaiting_sec = 30 )
{
   // at testing, there is no resaon to divide the trade context - just terminate 
   // the function
   if(IsTesting()) return(1);
    
   int _GetLastError = 0, StartWaitingTime = GetTickCount();
   //+------------------------------------------------------------------+
   //| Check whether a global variable exists and, if not, create it    |
   //+------------------------------------------------------------------+
   while(true)
   {
      // if the expert was terminated by the user, stop operation
      if(IsStopped()) 
      { 
         Print("The expert was terminated by the user!"); 
         return(-1); 
      }
      // if the waiting time exceeds that specified in the variable 
      // MaxWaiting_sec, stop operation, as well
      if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
      {
         Print("Waiting time (" + MaxWaiting_sec + " sec) exceeded!");
         return(-2);
      }
      // check whether the global variable exists
      // if it does, leave the loop and go to the block of changing 
      // LockTradingThread value
      if(GlobalVariableCheck( "LockTradingThread" )) 
         break;
      else
      // if the GlobalVariableCheck returns FALSE, it means that it does not exist or  
      // an error has occurred during checking
      {
         _GetLastError = GetLastError();
         // if it is still an error, display information, wait for 0.1 second, and 
         // restart checking
         if(_GetLastError != 0)
         {
            Print("LockTradingThread()-GlobalVariableCheck(\"LockTradingThread\")-Error #",
                    _GetLastError );
            Sleep(100);
            continue;
         }
      }
      // if there is no error, it means that there is just no global variable, try to create
      // it
      // if the GlobalVariableSet > 0, it means that the global variable has been successfully created. 
      // Leave the function
      if(GlobalVariableSet( "LockTradingThread", 1.0 ) > 0 ) 
         return(1);
      else
      // if the GlobalVariableSet has returned a value <= 0, it means that an error 
      // occurred at creation of the variable
      {
         _GetLastError = GetLastError();
         // display information, wait for 0.1 second, and try again
         if(_GetLastError != 0)
         {
            Print("LockTradingThread()-GlobalVariableSet(\"LockTradingThread\",0.0 )-Error #",
                    _GetLastError );
            Sleep(100);
            continue;
         }
      }
   }
   //+----------------------------------------------------------------------------------+
   //| If the function execution has reached this point, it means that global variable  | 
   //| variable exists.                                                                 |
   //| Wait until the LockTradingThread becomes = 0 and change the value of LockTradingThread for 1 |
   //+----------------------------------------------------------------------------------+
   while(true)
   {
      // if the expert was terminated by the user, stop operation
      if(IsStopped()) 
      { 
         Print("The expert was terminated by the user!"); 
         return(-1); 
      }
      // if the waiting time exceeds that specified in the variable 
      // MaxWaiting_sec, stop operation, as well
      if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
      {
         Print("The waiting time (" + MaxWaiting_sec + " sec) exceeded!");
         return(-2);
      }
      // try to change the value of the LockTradingThread from 0 to 1
      // if succeed, leave the function returning 1 ("successfully completed")
      if(GlobalVariableSetOnCondition( "LockTradingThread", 1.0, 0.0 )) 
         return(1);
      else
      // if not, 2 reasons for it are possible: LockTradingThread = 1 (then one has to wait), or 

      // an error occurred (this is what we will check)
      {
         _GetLastError = GetLastError();
         // if it is still an error, display information and try again
         if(_GetLastError != 0)
         {
            Print("LockTradingThread()-GlobalVariableSetOnCondition(\"LockTradingThread\",1.0,0.0 )-Error #",
            _GetLastError );
            continue;
         }
      }
      //if there is no error, it means that LockTradingThread = 1 (another expert is trading), then display 
      // information and wait...
      Print("Wait until another expert finishes trading...");
      Sleep(1000);
   }
}

/////////////////////////////////////////////////////////////////////////////////
// void UnlockTradingThread()
//
// The function sets the value of the global variable LockTradingThread = 0.
// If the LockTradingThread does not exist, the function creates it.
/////////////////////////////////////////////////////////////////////////////////
void UnlockTradingThread()
{
   int _GetLastError;
   // at testing, there is no sense to divide the trade context - just terminate 
   // the function
   if(IsTesting()) 
   { 
      return(0); 
   }
   while(true)
   {
      // if the expert was terminated by the user, ?????????? ??????
      if(IsStopped()) 
      { 
         Print("The expert was terminated by the user!"); 
         return(-1); 
      }
      // try to set the global variable value = 0 (or create the global 
      // variable)
      // if the GlobalVariableSet returns a value > 0, it means that everything 
      // has succeeded. Leave the function
      if(GlobalVariableSet( "LockTradingThread", 0.0 ) > 0) 
         return(1);
      else
      // if the GlobalVariableSet returns a value <= 0, this means that an error has occurred. 
      // Display information, wait, and try again
      {
         _GetLastError = GetLastError();
         if(_GetLastError != 0 )
            Print("UnlockTradingThread()-GlobalVariableSet(\"LockTradingThread\",0.0)-Error #", 
                 _GetLastError );
      }
      Sleep(100);
   }
}
User avatar
gaheitman
Trader
Posts: 655
Joined: Tue Nov 15, 2011 10:55 pm
Location: Richmond, VA, US

Re: Avoid Trade Context Errors

Post by gaheitman »

Just to "see the magic happen" I wrote an ea that enters 20 Buy orders at the current price. The EA waits until a user specified time and then tries to enter them all so that you can coordinate across multiple pairs.

I ran three tests, all trying to enter 20 trades for each of 26 currencies all starting at the same time.

Test 1: Regular OrderSend(), no error checking.
Of the 520 trades sent, only 20 made it! The first currency sent all it's trades successfully and the rest merrily tried and failed with [Trade Context Busy].

Code: Select all

   OrderSend(Symbol(), OP_BUY, MarketInfo(Symbol(),MODE_MINLOT), Ask, 10, 0, 0);
Test 2: Regular OrderSend(), IsTradeContextBusy() check.
Of the 520 trades, 53 still failed with the error check. At the moment of the check, the trade context was free, but when it tried to enter the trade someone else had grabbed the thread and we were blocked. Again, the error was [Trade Context Busy].

Code: Select all

   while (IsTradeContextBusy()) Sleep(100);
   OrderSend(Symbol(), OP_BUY, MarketInfo(Symbol(),MODE_MINLOT), Ask, 10, 0, 0);
Test 3: Modified _OrderSend, no error checking.
Of the 520 trades, 20 failed to enter because of a timeout waiting for the trading thread to become available. The timeout is currently 30 seconds, but that can be changed in the code. No [Trade Context Busy] errors, however.

Code: Select all

   _OrderSend(Symbol(), OP_BUY, MarketInfo(Symbol(),MODE_MINLOT), Ask, 10, 0, 0);
Just in case... DON'T USE THIS ON A LIVE ACCOUNT! (Unless you need to get a bunch of buy orders opened quickly. :lol: )

George
You do not have the required permissions to view the files attached to this post.
magft
Trader
Posts: 195
Joined: Tue Nov 15, 2011 9:59 pm
Location: East Midlands, UK

Re: Avoid Trade Context Errors

Post by magft »

Nice work George.

I actually have use a combination of test 2 and 3 in my EA! I was thinking what about instead of a fixed 100ms wait make it a random value between 50 and 500ms. This way when loads of versions of the EA run at the same time this may give a bit more breathing space for trades.

Just a thought.

Mike
garyfritz

Re: Avoid Trade Context Errors

Post by garyfritz »

It sounds like your test beat on it pretty hard and it worked well, George. But (geeky computer science pedantry here) this is NOT a safe semaphore. You can have situations like this:

EA1: Is the semaphore 0?
EA2: Is the semaphore 0?
EA1: Ah, it is! Great! I'll set it to 1 and place my order.
EA2: Ah, it is! Great! I'll set it to 1 and place my order.

Apparently that happened seldom enough that it wasn't much of a problem, and the way things work, it worked out OK. Maybe the way threads work in Empty4 it can't happen -- not sure. In any event it looks like your solution improves the situation a WHOLE lot!
User avatar
gaheitman
Trader
Posts: 655
Joined: Tue Nov 15, 2011 10:55 pm
Location: Richmond, VA, US

Re: Avoid Trade Context Errors

Post by gaheitman »

garyfritz wrote:It sounds like your test beat on it pretty hard and it worked well, George. But (geeky computer science pedantry here) this is NOT a safe semaphore. You can have situations like this:

EA1: Is the semaphore 0?
EA2: Is the semaphore 0?
EA1: Ah, it is! Great! I'll set it to 1 and place my order.
EA2: Ah, it is! Great! I'll set it to 1 and place my order.

Apparently that happened seldom enough that it wasn't much of a problem, and the way things work, it worked out OK. Maybe the way threads work in Empty4 it can't happen -- not sure. In any event it looks like your solution improves the situation a WHOLE lot!
Actually it should be safe after the initial creation of the variable. From that point forward it uses GlobalVariableSetOnCondition() to set the variable. According to the documentation, this provides atomic access to the global variable.

From the documentation:

bool GlobalVariableSetOnCondition( string name, double value, double check_value)

Sets the new value of the existing global variable if the current value equals to the third parameter check_value. If there is no global variable, the function will generate error ERR_GLOBAL_VARIABLE_NOT_FOUND (4058) and return FALSE. When successfully executed, the function returns TRUE, otherwise, it returns FALSE. To get the detailed error information, one has to call the GetLastError() function.
If the current value of the global variable differs from the check_value, the function will return FALSE.
The function provides atomic access to the global variable, this is why it can be used for providing of a semaphore at interaction of several experts working simultaneously within one client terminal.

George
garyfritz

Re: Avoid Trade Context Errors

Post by garyfritz »

Ah! OK, then I withdraw my pedantry. :oops: I must have read it wrong when I skimmed through the code. I thought I saw a "check" and then a "set."
User avatar
gaheitman
Trader
Posts: 655
Joined: Tue Nov 15, 2011 10:55 pm
Location: Richmond, VA, US

Re: Avoid Trade Context Errors

Post by gaheitman »

garyfritz wrote:Ah! OK, then I withdraw my pedantry. :oops: I must have read it wrong when I skimmed through the code. I thought I saw a "check" and then a "set."
Well, you were right too. :D The check is to see if it exists. If it doesn't it creates it by setting it to 1 (in use). It shouldn't do that because if the variable doesn't exist, two EAs could set it at the same time thinking they can trade. Once the variable exists, it should be safe.

I'll change the code to set it to 0 (not in use) to start, and if it makes it into the shell we should certainly use it that way. Or, try to create the variable in Init() when we aren't trying to trade.

George
jb68
Trader
Posts: 71
Joined: Mon Jan 30, 2012 11:30 pm

Re: Avoid Trade Context Errors

Post by jb68 »

Hi,

Changing the order will not change the race issue.
If u want to make it better you can implement a 2 stage individual locking semaphore. This is easy and u can do it with minor changes.

EA request access so it check global variable.
If no global variable then it will write it's own ID/Magic/Hash into the global.
If writing is atomic(or not) even if 2 EAs started to write on the same time only one final ID/Magic/hash will be written.
Now the EA will check the ID/Hash/Magic to confirm if is it's own ID/Magic/Magic before sending an order. After order has been sent it will delete the variable (set to 0).
mbkennel
Trader
Posts: 29
Joined: Tue Dec 20, 2011 5:40 am

Re: Avoid Trade Context Errors

Post by mbkennel »

I don't think this problem can be solved like this.

It sounds like it is very similar to the original Ethernet problem (multiple parties wanting to 'talk' on a single wire at the same time), and has the same solution: if you find it is busy, then wait a random time (distributed as an exponential).

I'll soon post my LibOrderReliable which has wrappers for most of the Order**** functions and some decent error checking.
"It is a capital mistake to theorize before one has data." Sir Arthur Conan Doyle.
"If your experiment needs statistics, you ought to have done a better experiment" Lord Rutherford.
Post Reply

Return to “Coders Hangout”