How I use Properties Files in Expert Advisers

Introduction



The classic way of defining input parameters for an expert adviser is using a variable, for example:
input double Lots=0.01; //Default lots to use
In this case the input variable will hold the default value to use in the expert adviser throughout the execution. If you want to change the value, you’d have to restart the expert adviser to enter the new value.
But, what if you could have avoided restarting the expert adviser, in some cases, the expert adviser is doing something or holding some valuable piece of information which might get lost if you restart it again.

My way of using parameters solves exactly that challenge, in my solution which I will explain later on, I am using properties files and updating the expert adviser on the fly, thus, avoiding the unnecessary restart of the expert adviser.

File Structure



As I am using the plain text files as properties files, the structure is mostly as key=value structure. For example, the Lots input parameter mentioned above will be written as:

Lots=0.01

In its own line.

As for the file name convention, I normally use the expert adviser name concatenate with “Properties.txt”. So, if the expert adviser is called Trader, there will be a file called TraderProperties.txt under the files folder with the relevant properties. You may form this file name programmatically by calling the following function:

string PropertiesFilename=StringConcatenate(MQLInfoString(MQL_PROGRAM_NAME) ,"Properties.txt");

Reading Properties Files on the Fly
Assuming your expert adviser is running with a certain setup and you want to fine tune it by changing one of the parameters you use. Like mentioned above, one option will be to restart the expert adviser and enter the new values, or, using the following code to check for updated properties. You may put this code with the OnTimer function to check on periods or to call it upon a certain condition.

Create a global variable to store the last time the properties file was modified, we will see how to use it within the code. For the moment, have this initialization in your code:
long PropertiesFileModifyDate;

Start by opening the file by calling the next set of functions:
string PropertiesFileName;
if(IsDemo()==true)
PropertiesFileName=StringConcatenate(MQLInfoString(MQL_PROGRAM_NAME) ,"DemoProperties.txt");
else
PropertiesFileName=StringConcatenate(MQLInfoString(MQL_PROGRAM_NAME) ,"Properties.txt");
if(FileIsExist(PropertiesFileName)==false)
{
Log(StringConcatenate("Properties file ",PropertiesFileName," doesn't exist, line ",__LINE__));
return false;
}

With these lines, we create the properties file name, we may distinguish between demo environment to production one as the code suggests, but it is not mandatory. I use as I have different properties for testing and for live accounts. If the file doesn’t exist, the function finishes here and return false to the calling environment.

Otherwise, we open the file for reading as a text file and check the return handler. If it’s invalid, we print an error and return false:
else
{
int PropertiesFileHandle=FileOpen(PropertiesFileName,FILE_TXT|FILE_READ);
if(PropertiesFileHandle==INVALID_HANDLE)
{
Log(StringConcatenate("Operation FileOpen failed, error ",ErrorDescription(GetLastError()),", line ",__LINE__));
return false;
}

Now that we opened the file successfully, we check whether its modify date is different than the one we store in the global variable. If it equals -1 we return an error, if it equals the global variable PropertiesFileModifyDate we do nothing and return:
long ModifyDate=FileGetInteger(PropertiesFileHandle,FILE_MODIFY_DATE);
if(ModifyDate==-1)
{
Log(StringConcatenate("Operation FileGetInteger failed, error ",ErrorDescription(GetLastError()),", line ",__LINE__));
FileClose(PropertiesFileHandle);
return false;
}
else if(ModifyDate==PropertiesFileModifyDate)
{
FileClose(PropertiesFileHandle);
return true;
}
Finally, if the file got changed, we update the global PropertiesFileModifyDate and read the file’s contents. In this simple example, I suppose there is a single value to read which is Lots, so I get the value and update the internal variable:
else
{
PropertiesFileModifyDate=ModifyDate;
string Line,Values[];
while(FileIsEnding(PropertiesFileHandle)==false)
{
Line=FileReadString(PropertiesFileHandle);
if(StringSplit(Line,StringGetCharacter("=",0),Values)!=2)
{
Log(StringConcatenate("Operation StringSplit return more than two values, line ",__LINE__));
return false;
}
if(StringCompare(Values[0],"Lots")==0)
Lots=StringToDouble(Values[1])==0;
}
FileClose(PropertiesFileHandle);
return true;
}
In the end of the reading process, I close the file and return true to the calling function. In the next calls, we will again compare the modify date and only read the file if necessary.

Putting it all together in a function we have:
bool SetInputVariables()
{
string PropertiesFileName;
if(IsDemo()==true)
PropertiesFileName=StringConcatenate(MQLInfoString(MQL_PROGRAM_NAME) ,"DemoProperties.txt");
else
PropertiesFileName=StringConcatenate(MQLInfoString(MQL_PROGRAM_NAME) ,"Properties.txt");
if(FileIsExist(PropertiesFileName)==false)
{
Log(StringConcatenate("Properties file ",PropertiesFileName," doesn't exist, line ",__LINE__));
return false;
}
else
{
int PropertiesFileHandle=FileOpen(PropertiesFileName,FILE_TXT|FILE_READ);
if(PropertiesFileHandle==INVALID_HANDLE)
{
Log(StringConcatenate("Operation FileOpen failed, error ",ErrorDescription(GetLastError()),", line ",__LINE__));
return false;
}
long ModifyDate=FileGetInteger(PropertiesFileHandle,FILE_MODIFY_DATE);
if(ModifyDate==-1)
{
Log(StringConcatenate("Operation FileGetInteger failed, error ",ErrorDescription(GetLastError()),", line ",__LINE__));
FileClose(PropertiesFileHandle);
return false;
}
else if(ModifyDate==PropertiesFileModifyDate)
{
FileClose(PropertiesFileHandle);
return true;
}
else
{
PropertiesFileModifyDate=ModifyDate;
string Line,Values[];
while(FileIsEnding(PropertiesFileHandle)==false)
{
Line=FileReadString(PropertiesFileHandle);
if(StringSplit(Line,StringGetCharacter("=",0),Values)!=2)
{
Log(StringConcatenate("Operation StringSplit return more than two values, line ",__LINE__));
return false;
}
if(StringCompare(Values[0],"Lots")==0)
Lots=StringToDouble(Values[1])==0;
return true;
}
}
}

With Log utility function as follows (my own implementation):
void Log(string argMessage,bool argSendNotificationFlag=true,int argErrorCode=0) export
{
if(argErrorCode!=0)
argMessage=StringConcatenate(argMessage,", Error: ",argErrorCode," - ",ErrorDescription(argErrorCode));

Print(StringConcatenate(AccountServer(),", ",IntegerToString(AccountNumber()),", ",MQLInfoString(MQL_PROGRAM_NAME),", ",Symbol()," ",argMessage,", Balance = ",DoubleToString(AccountBalance(),2)," ",AccountCurrency(),", P&L = ",DoubleToString(AccountProfit(),2)," ",AccountCurrency(),", Equity = ",DoubleToString(AccountEquity(),2)," ",AccountCurrency(),", Margin = ",DoubleToString(AccountMargin(),2)," ",AccountCurrency(),", Margin (%) = ",DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL),2),"%"));
if(argSendNotificationFlag==true)
SendNotification(StringConcatenate(AccountServer(),", ",IntegerToString(AccountNumber()),", ",MQLInfoString(MQL_PROGRAM_NAME),", ",Symbol()," ",argMessage,", Balance = ",DoubleToString(AccountBalance(),2)," ",AccountCurrency(),", P&L = ",DoubleToString(AccountProfit(),2)," ",AccountCurrency(),", Equity = ",DoubleToString(AccountEquity(),2)," ",AccountCurrency(),", Margin = ",DoubleToString(AccountMargin(),2)," ",AccountCurrency(),", Margin (%) = ",DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL),2),"%"));
}

Usage



Equipped with this code, you may update the properties files with new parameter values and put on the relevant environment. Then, upon specific timing or condition you set in your expert adviser, you can call this function and update the parameters on the fly:


  1. From within OnInit function

  2. From within OnTimer function

  3. Upon a certain code condition



The only occasions you need to compile the original expert adviser and restart it is if you introduce new properties to take into consideration or changed its logic.






Comments