How is your code organized? - page 2

 
@paulpanke Great tips. Thanks.
 
fxjozef:

@sokramm presumably you don't write DLLs in mql4 but rather in C/C++, right? you keep mql4 stuff in mqh and general stuff (logging, database access, etc.) in DLL?


My DLL's are in C++. Anything that is not strictly related to trading (file handling, databases, external tools) I code it in C++ and then call it when I need it from the respective mqh. I keep my mqh with code that needs mql4 libraries.

Some may say that it is not the best approach for speed, but currently my trading strategy is not so concerned about speed, and I care more about portability and having to some day rewrite my code in mql6 (or mql5 if I have to...) or any other trading platform that supports Win DLL's.

 
paulepanke:

- write wrappers around the MQL init(), start() and deinit() functions, the MetaQuotes implementations are to limited for production-ready code. my wrappers are in include\core\...


Are you referring to the error checking or to something else? Could you explain what you mean? Thanks
 
sokramm: Are you referring to the error checking or to something else? Could you explain what you mean? Thanks

yes, but not only. the wrappers provide a "safe environment" for code execution (includes error handling). it means i provide a lot of global variables to MQL code (many more than provided by MetaQuotes), store run-time status so the code can react to run-time status change, initialize loggers etc. indicators experts, scripts behave differently in init(), start(), deinit() and they all cause/are influenced by different bugs specific to ea/indicator/script. instead of trying to workaround the same bugs in every EA (for example) every module type (ea, indicator, script, library) has its own base header (in "include\core\..."). my aim was to provide some kind of pluggable MQL environment for fast prototyping of new code.

for ex. this includes the possibility to react to different UNINITIALIZE_REASONs in deinit() and init() without the need to write endless switch/if/else statements. like in other languages i only want to implement some kind of event handler and it should work out of the box.

the empty TestExpert from above:

/**
 * TestExpert
 */
#include <stddefine.mqh>
int   __INIT_FLAGS__[];
int __DEINIT_FLAGS__[];
#include <stdlib.mqh>
#include <win32api.mqh>

#include <core/expert.mqh>


/**
 * Main-Funktion
 *
 * @return int - Fehlerstatus
 */
int onTick() {
   return(last_error);
}

it compiles fine. now i want to invoke some code similar to an event handler. out of the box it goes like this

/**
 * TestExpert
 */
#include <stddefine.mqh>
int   __INIT_FLAGS__[];
int __DEINIT_FLAGS__[];
#include <stdlib.mqh>
#include <win32api.mqh>

#include <core/expert.mqh>


/**
 * Main-Funktion
 *
 * @return int - Fehlerstatus
 */
int onTick() {
   return(last_error);
}


/**
 *
 * @return int - Fehlerstatus
 */
int onInitParameterChange() {
   bool interactive = true;

   //StoreConfiguration();
   //if (!ValidateConfiguration(interactive))
   //   RestoreConfiguration();

   return(last_error);
}

again it compiles without further change. from "userland code" it looks like all the onInit* and onDeinit* functions are provided by the terminal. but they are not, it's the wrapper.

now i can concentrate on my Validate() logic and don't need to worry about if and when code is executed. this is what i mean with fast prototyping. in the global EA:init() for example i have this

   // (1) re-initialize global vars
   PipDigits      = Digits & (~1);                                        SubPipDigits      = PipDigits+1;
   PipPoints      = Round(MathPow(10, Digits<<31>>31));                   PipPoint          = PipPoints;
   Pip            = NormalizeDouble(1/MathPow(10, PipDigits), PipDigits); Pips              = Pip;
   PipPriceFormat = StringConcatenate(".", PipDigits);                    SubPipPriceFormat = StringConcatenate(PipPriceFormat, "'");
   PriceFormat    = ifString(Digits==PipDigits, PipPriceFormat, SubPipPriceFormat);

   // (2)  enable experts
   int reasons1[] = { REASON_UNDEFINED, REASON_CHARTCLOSE, REASON_REMOVE };
   if (!IsTesting()) /*&&*/ if (!IsExpertEnabled()) /*&&*/ if (IntInArray(reasons1, UninitializeReason())) {
      error = Toolbar.Experts(true);                                          
      if (IsError(error))
         return(SetLastError(error));
   }

   // (3) reset order context after ea::reload (@see order context bug)
   int reasons2[] = { REASON_UNDEFINED, REASON_CHARTCLOSE, REASON_REMOVE, REASON_ACCOUNT };
   if (IntInArray(reasons2, UninitializeReason()))
      OrderSelect(0, SELECT_BY_TICKET);

   ...

this all are things i don't want to do again and again and again. i don't want to worry about 4 or 5 digit brokers. all this belongs in a global include that handles all situations specific to this type of module (T_EXPERT|T_SCRIPT|T_INDICATOR). i want  to concentrate on business logic instead on MT4 "features" in a portable and compatible way.

what if i update one of the global includes? in windows file explorer i go to the experts folder, search for "*.ex4; mqlcache*" and delete everything. then i restart the terminal and wait a minute. if there's code i dont have the source code for i do a "Synchronize" with my repository located at a internet server. ex4 files without source code are stored in repository. now it doesn't matter if i change code at home, at the restaurant or at a clients office. everything it needs are one "delete" and one "synchronize".

 
additionally i have four MT4 installations running on the same code and history base linked via "Link Shell Extension" (i'm running XP) for development and two MT4 installations acting as "Strategy Runners" running on another copy of the same code base. the StratRunners run on the same history base as the development environment (again linked). both different code bases are linked to the same repository (under different checkout/project names). this enables me to develop, test, commit and distribute code *without* influencing the production environment. the prod environment is synchronized separately. all six installations are on my laptop, another copy at my home pc. on a VPS (in fact i don't use a VPS, i have my own servers) resides the real production environment. there are four installations linked only to the prod repository. i login via ssh or RemoteDesktop and can even trigger a Synchronize from my phone (i don't need this feature).
 

how to make the onInit*() and onDeinit*() functions behave like build-in without compiler complains:

stdlib.mqh:

stdlib.mq4:

this way you only need to implement what you really need/want. of course you can keep the function bodies empty, this is just my personal default implementation.

with this you can, for example, write a script or an expert with all the logic in "init()", you don't need a "start()" function and the compiler doesn't complain. if it makes sense to you.

 

Ok, thanks for the response. In my case, I also never change my Init or deInit functions for new EA's.

I have a mqh where the trading logic is specified based on signals and indicators. I just need to change that part and I have a new EA with everything else (logging, error checking, trade management) working around it without extra effort.

Just a configuration file in case of needed adjustments.

 

Hi all,

You can use tortoisesvn to manage your code. 

Regards,

 
sontranngoc:

Hi all,

You can use tortoisesvn to manage your code. 

Regards,

But all the cool kids are using git now

 
nicholi shen:

But all the cool kids are using git now

True that.

SVN is so last decade.