1. What Is "Trade Context" in Terms of MetaTrader 4 Client Terminal
Extract from MetaEditor Reference:
To trade from experts and scripts, only one thread
was provided that was launched in the program trade context (context of
automated trading from experts and scripts). This is why, if this
context is occupied with an expert trading operation, another expert or
script cannot call trading functions at that moment due to error 146
(ERR_TRADE_CONTEXT_BUSY).
Better to say, only
one expert (script) can trade at a time. All other experts that try to
start trading will be stopped by Error 146. This article will find
solutions for this problem.
2. Function IsTradeAllowed()
The simplest way to find out whether the trade context is busy is to use the function named IsTradeAllowed().
Extract from MetaEditor Reference:
"bool IsTradeAllowed()
Returns true if the expert is allowed to trade and a thread for trading is not occupied, otherwise returns false.
This means that one can try to trade only if the IsTradeAllowed() function returns TRUE.
The check must be done just before a trade operation.
An example of wrong usage of the function:
int start()
{
if(!IsTradeAllowed())
{
Print("Trade context is busy! The expert cannot open position!");
return(-1);
}
else
{
Print("Trade context is free! We go on working...");
}
...
...
if(OrderSend(...) < 0)
Alert("Error opening position # ", GetLastError());
return(0);
}
In this example, the trade context status is checked at the very
beginning of the start() function. It is a wrong idea: The trade
context can be occupied by another expert during the time taken by our
expert to calculate everything (the necessity to enter the market, Stop
Loss and Take Profit levels, lot size, etc.). In such case, the attempt
to open a position will not succeed.
An example of proper usage of the function:
int start()
{
...
...
if(!IsTradeAllowed())
{
Print("Trade context is busy! The expert cannot open position!");
return(-1);
}
else
Print("Trade context is free! Trying to open position...");
if(OrderSend(...) < 0)
Alert("Error opening position # ", GetLastError());
return(0);
}
The trade context status is checked here immediately
before opening of position, and probability that another expert will
interpose between these two actions is much less. (It still exists,
though. This will be considered below.)
This method has two essential disadvantages:
- it is still probable that experts will check status simultaneously
and, having received positive results, will try to trade at the same
time
- if the checking fails, the expert will try to trade again only at the next tick; such delay is highly undesirable
The second problem can be
solved rather easily: one has just to wait until the trade context
becomes free. Then the expert will start trading immediately after the
other expert has finished it.
This will probably look like this:
int start()
{
...
...
if(!IsTradeAllowed())
{
Print("Trade context is busy! Wait until it is free...");
while(true)
{
if(IsStopped())
{
Print("The expert was stopped by the user!");
return(-1);
}
if(IsTradeAllowed())
{
Print("Trade context has become free!");
break;
}
Sleep(100);
}
}
else
Print("Trade context is free! Trying to open a position...");
if(OrderSend(...) < 0)
Alert("Error opening position # ", GetLastError());
return(0);
} In this current realization, we have some problem points again:
- since the IsTradeAllowed()
function is responsible not only for the trade context status, but also
for the enabling/disabling experts to trade, the expert can "hang" in
an infinite loop; it will then stop only if it is removed from the
chart manually
- if
the expert waits until the trade context is free, for just some
seconds, prices can change, and it will be impossible to trade using
them - the data should be refreshed, the open, Take Profit and Stop
Loss levels of the position to be opened should be recalculated
The corrected code will look like this:
int MaxWaiting_sec = 30;
int start()
{
...
...
if(!IsTradeAllowed())
{
int StartWaitingTime = GetTickCount();
Print("Trade context is busy! Wait until it is free...");
while(true)
{
if(IsStopped())
{
Print("The expert was stopped by the user!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("The standby limit (" + MaxWaiting_sec + " sec) exceeded!");
return(-2);
}
if(IsTradeAllowed())
{
Print("Trade context is free!");
RefreshRates();
...
break;
}
Sleep(100);
}
}
else
Print("Trade context is free! Trying to open a position...");
if(OrderSend(...) < 0)
Alert("Error opening position # ", GetLastError());
return(0);
} In the above example, we added:
- refreshing of the market info (RefreshRates()) and consequent Stop Loss and Take Profit recalculation
- maximal time of waiting MaxWaiting_sec, after exceeding of which the expert will stop operation
As such, the above code can be used in your experts already.
The final touch: Let us put
all concerning checking into a separate function. This will simplify
its integration in experts and its usage.
//
//
int _IsTradeAllowed(int MaxWaiting_sec = 30)
{
if(!IsTradeAllowed())
{
int StartWaitingTime = GetTickCount();
Print("Trade context is busy! Wait until it is free...");
while(true)
{
if(IsStopped())
{
Print("The expert was terminated by the user!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("The waiting limit exceeded (" + MaxWaiting_sec + " сек.)!");
return(-2);
}
if(IsTradeAllowed())
{
Print("Trade context has become free!");
return(0);
}
Sleep(100);
}
}
else
{
Print("Trade context is free!");
return(1);
}
}
A template for the expert that uses the function:
int start()
{
...
...
int TradeAllow = _IsTradeAllowed();
if(TradeAllow < 0)
{
return(-1);
}
if(TradeAllow == 0)
{
RefreshRates();
...
}
if(OrderSend(...) < 0)
Alert("Error opening position # ", GetLastError());
return(0);
} Let us draw some conclusions:
The IsTradeAllowed()
function is easy to use and ideally suits for differentiation of
accesses to trade context for two or three experts working
simultaneously. Due to some disadvantages thereof, its use does
not ensure from Error 146 when many experts work simultaneously.
It can also cause "hanging" of the expert if the "Allow live trading"
is disabled.
This is why we will consider an alternative solution for this problem -a global variable as a "semaphore".
3. Client Terminal Global Variables
First, the definition:
The client terminal global
variables are variables accessible to all experts, scripts and
indicators. This means a global variable created by one expert can be used in other experts
(in our case, to distribute accesses).
There are several functions provided in MQL 4 to work with global variables:
- GlobalVariableCheck() - to check whether a global variable exists
- GlobalVariableDel() - to delete a global variable
- GlobalVariableGet() - to get the value of the global variable
- GlobalVariableSet() - to create or modify a global variable
- GlobalVariableSetOnCondition() - to change the value of the
global variable specified by the user for another one. It differs from
GlobalVariableSet() in that the new value will be set only at a certain
previous value.
It is this function, which is a key function to create a semaphore.
- GlobalVariablesDeleteAll() - to delete all global variables (I cannot imagine who may need this:)
Why should the
GlobalVariableSetOnCondition() be used, but not the combination of
functions GlobalVariableGet() and GlobalVariableSet()? For the same
reasons: Some time can ellapse between uses of two functions. And
another expert can interpose into the semaphore switching. But this is
not what we need.
4. The Basic Concept of Semaphore
Expert that is going to
trade should check the semaphore status. If the semaphore shows "red
light" (global variable = 1), it means that another expert is trading,
so it is necessary to wait. If it shows "green light"
(global variable = 0), the trading can be started immediately (but not
to forget to set the "red light" for other experts).
Thus, we have to create 2
functions: one for setting the "red light", another one for setting the
"green light". On the face of it, the task is simple. But we will not
jump to conclusions, but try to formulate the sequence of actions to be
executed by each function (let as name them TradeIsBusy() and
TradeIsNotBusy()) and finally realize them.
5. Function TradeIsBusy()
As has been said before, the main task of the
function will be to wait until the "green light" appears and to switch
on the "red light".
Besides, we have to check whether the global variable exists, and
create it, if not. This checking would be more reasonable (and more
efficient) to perform from the init() function
of the expert.
But then a probability could exist that the user would delete it and no
one of working expert would be able to trade. This is why we will place
it in the body of the created function.
All this should be accompanied by information displaying and processing
of errors that have occurred when working with the global variable. The
"hanging" should not be forgotten, as well: The function operation time
should be limited.
This is what we will finally get:
//
int TradeIsBusy( int MaxWaiting_sec = 30 )
{
if(IsTesting())
return(1);
int _GetLastError = 0, StartWaitingTime = GetTickCount();
while(true)
{
if(IsStopped())
{
Print("The expert was terminated by the user!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("Waiting time (" + MaxWaiting_sec + " sec) exceeded!");
return(-2);
}
if(GlobalVariableCheck( "TradeIsBusy" ))
break;
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableCheck(\"TradeIsBusy\")-Error #",
_GetLastError );
Sleep(100);
continue;
}
}
if(GlobalVariableSet( "TradeIsBusy", 1.0 ) > 0 )
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0 )-Error #",
_GetLastError );
Sleep(100);
continue;
}
}
}
while(true)
{
if(IsStopped())
{
Print("The expert was terminated by the user!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("The waiting time (" + MaxWaiting_sec + " sec) exceeded!");
return(-2);
}
if(GlobalVariableSetOnCondition( "TradeIsBusy", 1.0, 0.0 ))
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableSetOnCondition(\"TradeIsBusy\",1.0,0.0 )-Error #",
_GetLastError );
continue;
}
}
Comment("Wait until another expert finishes trading...");
Sleep(1000);
Comment("");
}
}
Well, everything seems to be clear here:
- checking for whether the global variable exists and, if not, creation of it
- attempt to change the value of the global variable from 0 to 1; it will trigger only if its value is = 0.
The maximum amount of
seconds during which the function can work is MaxWaiting_sec. The
function is no objection to deletion of the expert from the chart.
Information about all errors that occur is shown in the log.
6. Function TradeIsNotBusy()
The TradeIsNotBusy function solves the inverse problem: It switches the "green light" on.
It is not limited by the
operation time and cannot be terminated by user. Motivation is rather
simple: If the "green light" is off, no expert will be able to trade.
It does not naturally have any return codes: The result can be only a successful completion.
This is how it looks:
//
void TradeIsNotBusy()
{
int _GetLastError;
if(IsTesting())
{
return(0);
}
while(true)
{
if(IsStopped())
{
Print("The expert was terminated by the user!");
return(-1);
}
if(GlobalVariableSet( "TradeIsBusy", 0.0 ) > 0)
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0 )
Print("TradeIsNotBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0)-Error #",
_GetLastError );
}
Sleep(100);
}
}
7. Integration into Experts and Use
Now we have 3 functions to
distribute access to the trading flow. To simplify their integration
into experts, we can create the TradeContext.mq4 file and enable it
using the #include directive (file attached).
Here is the template of the expert that uses functions TradeIsBusy() and TradeIsNotBusy():
#include <TradeContext.mq4>
int start()
{
...
...
if(TradeIsBusy() < 0)
return(-1);
RefreshRates();
...
if(OrderSend(...) < 0)
{
Alert("Error opening position # ", GetLastError());
}
TradeIsNotBusy();
return(0);
}
In the use of functions
TradeIsBusy() and TradeIsNotBusy(), only one problem can occur: If the
expert is removed from the chart after the trade context has become
busy, the variable TradeIsBusy will remain equal to 1. Other experts
will not be able to trade after that.
The problem can be solved easily: The expert should not be removed from the chart when it is trading ;)
It is also possible that the
variable TradeIsBusy is not zeroized at disorderly close-down of the
terminal. In this case, the TradeIsNotBusy() function from the expert's
init() function can be used.
And, of course, the value of
the variable can be changed manually at any time: F3 button in the
terminal (it is an undocumented possibility to disable all experts to
trade.
komposter (komposterius@mail.ru), 2006.04.11
Translated from Russian by MetaQuotes Software Corp.
Original article: http://articles.mql4.com/ru/94