Code migration from EasyLanguage, MQL4, C#, AFL, Equilla, PineScript...

Before starting serious algorithmic trading with Zorro, you might have used a different trading platform or language, and would like to take over your familiar automated trading strategies, algorithms, or indicators. Zorro can directly execute R and Python functions or functions from Windows DLLs. For scripts from other platforms you'll find below some hints about comparing backtest results and converting code to lite-C or C++ for Zorro.

Comparing backtest results and charts between different platforms

The same algo trading strategy can produce very different backtests and charts on different platforms. Especially MT4/MT5 backtests are often not comparable with results of other platforms. Here's how to find out why you got a different - usually more realistic - backtest after running your strategy on Zorro:

TradeStation™, MultiCharts™, TradeSignal™

TradeStation was the first platform that supported automated trading. Its EasyLanguage™, also used by MultiCharts and in a variant by TradeSignal. has a similar design philosophy as Zorro's lite-C. Although the syntax is a mix of C and Pascal, conversion to lite-C is relatively straightforward. EasyLanguage Vars are equivalent to lite-C data series. The first element of a series has no index, thus MySeries in EasyLanguage is the same as MySeries[0] in C. Keywords are case insensitive and local variables are preserved between function calls. Array indices start with 0 as in C, but in many code examples you find them starting with 1. So the arrays are probably allocated with 1 extra element. Trigonometric functions (Sine, Cosine etc) expect angles in degrees (0..360), while in C and in most other languages angles are in radians (0..2*PI). Log is the logarithm base e as in C.

EasyLanguage has no native functions, which is a strong limitation to complex projects. But separate scripts can be called like functions. The execution of an EasyLanguage script is similar to a lite-C script, with a lookback period determined by the MaxBarsBack variable. Aside from the function definitions, EasyLanguage strategies and lite-C strategies look very similar.

{ Easylanguage version }
{ Choppy Market Index Function by George Pruitt }
Inputs: periodLength(Numeric); Vars: num(0),denom(1);
if(periodLength <> 0) then
begin
denom = Highest(High,periodLength) – Lowest(Low,periodLength);
num = Close[periodLength-1] – Close;
ChoppyMarketIndex = 0.0;
if(denom <> 0) then ChoppyMarketIndex = AbsValue(num/demon)*100;
end;
// Zorro version (lite-C)
// Choppy Market Index Function by George Pruitt
var ChoppyMarketIndex(int periodLength) { if(periodLength != 0) {
var denom = HH(periodLength) – LL(periodLength);
var num = priceClose(periodLength-1) – priceClose(0);
if(denom != 0) return abs(num/denom)*100; } return 0;
}
{ Easylanguage version }
{ enter a trade when the RSI12 crosses over 75 or under 25 }
Inputs:  
    Price(Close),LengthLE(20),LengthSE(20),
    OverSold(25),OverBought(75),StoplossPips(100),ProfitTargetPips(100);
 
variables:  
    var0(0),var1(0);
 
{ get the RSI series }
var0 = RSI( Price, LengthLE );
var1 = RSI( Price, LengthSE );
 
{ if rsi crosses over buy level, exit short and enter long }
condition1 = Currentbar > 1 and var0 crosses over OverBought ;
if condition1 then                                                                    
    Buy( "RsiLE" ) next bar at market;
 
{ if rsi crosses below sell level, exit long and enter short }
condition2 = Currentbar > 1 and var1 crosses under OverSold ;
if condition2 then                                                                    
    Sell Short( "RsiSE" ) next bar at market;
 
{ set up stop / profit levels }
SetStoploss(StoplossPips);
SetProfitTarget(ProfitTargetPips);
// Zorro version (lite-C)
// enter a trade when the RSI12 crosses over 75 or under 25
function run()
{
  BarPeriod = 60;
// get the RSI series
  vars RSIs = series(RSI(seriesC(),20));
 
// set stop / profit levels and trade duration
  Stop = 100*PIP;
  TakeProfit = 100*PIP;
  LifeTime = 24;
 
// if rsi crosses over buy level, exit short and enter long
  if(crossOver(RSI12,75))
    enterLong();
// if rsi crosses below sell level, exit long and enter short
  if(crossUnder(RSI12,25))
    enterShort();
}

"Meta"-Trader 4 and 5

MT4 and MT5 are popular platform for retail traders and provided by many brokers. The MQL4 / MQL5 script language of their "Expert Advisors" (EAs) is based on C, which would theoretically allow easy conversion to Zorro's lite-C. Unfortunately, MQL4 has some issues that make "Expert Advisors" more complex and difficult to convert than scripts of other platforms. MQL5 has even more issues, and consequently so far has not managed to replace the older MQL4.
  Except for stop and profit targets, MQL4 trades are not managed by the platform, but must be managed by code in the script. Series are not natively supported, but must be emulated with loops and functions. Some indicators produce different results in MQL4 than in other platforms because they are not based on the standard algorithms, but on special MQL4 variants. Candles are based on local time, and account parameters are not normalized, so any EA must be individually adapted to the country, broker, and account. To complicate matters further, MQL4 does not use common trade units such as lots and pips, but calculates with "standard lots" and "points" that need to be multiplied with account-dependent conversion factors. Most code in an EA is therefore not used for the trade logic, but for patching all those issues. This results in the long and complex 'spaghetti code' that is typical for MT4 EAs.
  For conversion, first remove the MQL4/MQL5 specific code that is not needed in lite-C, such as trade management loops, broker dependent pip and trade size calculations, and array loops that emulate series. Then the rest can be converted by replacing the MQL4 indicators and trade commands by their lite-C equivalents. Replace OnTick either with tick or run, dependent on whether the code is bar or tick based. Bar indexes are normally shifted by 1, since they refer to the new (incomplete) bar, not to the last (complete) bar. Different indicator algorithms have to be converted or replaced.

// MQL4 version of the RSI strategy (see above)
// enter a trade when the RSI12 crosses over 75 or under 25
int start()
{
// get the previous and current RSI values
   double current_rsi = iRSI(Symbol(), Period(), 12, 
     PRICE_CLOSE, 1); // mind the '1' - candle '0' is incomplete!!
   double previous_rsi = iRSI(Symbol(), Period(), 12, PRICE_CLOSE, 2);
 
// set up stop / profit levels
   double stop = 200*Point;
   double takeprofit = 200*Point;

// correction for prices with 3, 5, or 6 digits
   int digits = MarketInfo(Symbol(), MODE_DIGITS);
if (digits == 5 || digits == 3) {
stop *= 10; takeprofit *= 10; } else if (digits == 6) {
stop *= 100; takeprofit *= 100; } // find the number of trades int num_long_trades = 0; int num_short_trades = 0; int magic_number = 12345;
// exit all trades in opposite direction for(int i = 0; i < OrdersTotal(); i++) { // use OrderSelect to get the info for each trade if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; // Trades not belonging to our EA are also found, so it's necessary to // compare the EA magic_number with the order's magic number if(magic_number != OrderMagicNumber()) continue; if(OrderType() == OP_BUY) { // if rsi crosses below sell level, exit long trades if((current_rsi < 25.0) && (previous_rsi >= 25.0)) OrderClose(OrderTicket(),OrderLots(),Bid,3,Green); else // otherwise count the trades num_long_trades++; } if(OrderType() == OP_SELL) { // if rsi crosses over buy level, exit short trades if((current_rsi > 75.0) && (previous_rsi <= 75.0)) OrderClose(OrderTicket(),OrderLots(), Ask,3,Green); else // otherwise count the trades num_short_trades++; } } // if rsi crosses over buy level, enter long if((current_rsi > 75.0) && (previous_rsi <= 75.0) && (num_long_trades == 0)) { OrderSend(Symbol(),OP_BUY,1.0,Ask,3,Ask-stop,Bid+takeprofit,"",magic_number,0,Green); } // if rsi crosses below sell level, enter short if((current_rsi < 25.0) && (previous_rsi >= 25.0) && (num_short_trades == 0)) { OrderSend(Symbol(),OP_SELL,1.0,Bid,3,Bid+stop,Ask-takeprofit,"", magic_number,0,Green); } return(0); }
Under Tips & Tricks you can find an example how to replicate MQ4-style indicator parameters with Zorro. The above mentioned issues also apply to MQL5, the "Meta"-Trader 5 script language. It has some similarities to MQL4, but offers C++ language elements and requires even more complex code for opening and managing trades.
 

NinjaTrader™

NinjaScript™ is based on C# and thus similar in syntax to Zorro's lite-C. NinjaScript also supports data series in the same way as lite-C, and its basic function list is very similar; this makes script migration rather easy. One major difference is that all NinjaTrader indicator functions return data series, while Zorro indicators return single values. Use the series function (f.i. series(indicator(..))) for making Zorro indicators also return series.

// NinjaTrader version
// Trade when a fast SMA crosses over a slow SMA
protected override void Initialize()
{ // Run OnBarUpdate on the close of each bar
CalculateOnBarClose = true; // Set stop loss and profit target at $5 and $10 SetStopLoss(CalculationMode.Ticks,5); SetProfitTarget(CalculationMode.Ticks,10); } protected override void OnBarUpdate()
{ // don't trade during the LookBack period if(CurrentBar < 20)
return; double Fast = 10; double Slow = 20; // Exit short and go long if 10 SMA crosses over 20 SMA
if(CrossAbove(SMA(Close,Fast),SMA(Close,Slow),1)) { ExitShort();
EnterLong(); } // Exit long and go short if 10 SMA crosses under 20 SMA
else if(CrossBelow(SMA(Close,Fast),SMA(Close,Slow),1)) { ExitLong();
EnterShort(); } }
// Zorro version
// Trade when a fast SMA crosses over a slow SMA
void run()
{ // Set stop loss and profit target at $5 and $10 Stop = 5; TakeProfit = 10; vars SMAFast = series(SMA(seriesC(),10)); vars SMASlow = series(SMA(seriesC(),20)); // Exit short and go long if 10 SMA crosses over 20 SMA
if(crossOver(SMAFast,SMASlow))
enterLong(); // Exit long and go short if 10 SMA crosses under 20 SMA
else if(crossUnder(SMAFast,SMASlow)) enterShort(); }
 

Amibroker™

Amibroker's AFL™ language is a C dialect, with similar syntax as lite-C. But the script structure is different. Amibroker uses a "Buy" and "Sell" variable for entering trades, instead of a function call. On the other hand, Amibroker calls functions for setting system parameters, instead of using variables. This unusual concept has its reason in a sort of vectorized approach to a trading system, where the code mainly initializes parameters and signal conditions. Variables are not declared, and they are usually series, as in EasyLanguage. Functions are often very similar to lite-C - for instance, Plot() or Optimize(). Others can be easily converted, f.i. Amibroker's "Explore" feature is equivalent to Zorro's print(TO_CSV).

// Amibroker version of the SMA crossing system (see above)
// Trade when a fast SMA crosses over a slow SMA
// Set stop loss and profit target at $5 and $10 ApplyStop(stopTypeLoss,stopModePoint,5,0,True,0,0,-1); ApplyStop(stopTypeProfit,stopModePoint,10,0,True,0,0,-1); SMAFast = MA(C,10); SMASlow = MA(C,20); // Buy if 10 SMA crosses over 20 SMA
Buy = Cross(SMAFast,SMASlow); // Sell if 10 SMA crosses under 20 SMA
Sell = Cross(SMASlow,SMAFast);

TradingView™

TradingView is a charting tool with a proprietary language named PineScript™ for defining indicators. Variables are declared by assigning a value to them, and language blocks are defined by indentation, as in Python. Functions are defined with '=>'. Conversion to C is normally pretty straightforward. Example for a Simple Moving Average:

// PineScript version
// SMA definition
study("My sma")
my_sma(price, length) =>
  sum = price
  for i = 1 to length-1
    sum := sum + price[i]
  sum / length
// Zorro version
// SMA definition
var my_sma(vars Prices,int Length) { var Sum = Prices[0]; for(i = 1; i < Length; i++) Sum += Prices[i]; return Sum/Length; }
 

Neuroshell Trader™

Neuroshell Trader is a platform specialized in employing a linear neural network for automated trading. Neuroshell indicators are functions added through DLLs. They take an input array, an output array, the array size, and additional parameters. Many other trade platforms use similar DLL based indicators. Such indicators are often based on normal C, thus conversion to Zorro is very easy - especially when you don't have to convert it at all and can call the DLL function directly.
   When using an indicator made for a different platform, the array order convention must be take care of. Neuroshell stores time series in ascending order (contrary to most other platforms that store them in reverse order) and passes the end of the array, not its begin, to the indicator function. Neuroshell indicators normally return an output series instead of a single value. Below both methods of indicator conversion are shown.

// Neuroshell version - Entropy indicator
// published by ForeTrade Technologies (www.foretrade.com/entropy.htm)
#include "math.h"
#include "stdlib.h"

__declspec(dllexport) void Entropy (double *price, double *entropy, long int size, long int numbars)
{
  double *in, *out, P, G;
  long int i,j;
  double sumx = 0.0;
  double sumx2 = 0.0;
  double avgx = 0.0;
  double rmsx = 0.0;

  in=price;
  out=entropy;

  for (i=0; i<size; i++)
  {
    if (i < numbars+1) *out = 3.4e38;
    else 
    {
      sumx = sumx2 = avgx = rmsx = 0.0;
      for (j=0;j<numbars+1;j++)
      {
        sumx += log(*(in-j) / *(in-j-1)) ;
        sumx2 += log(*(in-j) / *(in-j-1)) * log(*(in-j) / *(in-j-1));
      }
      if (numbars==0) 
      {
        avgx = *in;
        rmsx = 0.0;
      }
      else 
      {
        avgx = sumx / numbars;
        rmsx = sqrt(sumx2/numbars);
      }
      P = ((avgx/rmsx)+1)/2.0;
      G = P * log(1+rmsx) + (1-P) * log(1-rmsx);
      *out=G;
    }
    in++; out++;
  }
}
// Zorro version - Entropy indicator
// Method 1 - calling the DLL function directly
// Copy the Entropy DLL into the Zorro folder
int Entropy(double *price, double *entropy, long size, long numbars); // function prototype
API(Entropy,entropy) // use the Entropy function from entropy.dll

var EntropyZ(vars Data,int Period)
{
  Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size 
  double E; // single element "array" for receiving the output value 
  Entropy(
    Data+Period+1, // end of the array (element order does not matter here)
    &E, 1,         // pointer to the output "array" with size 1
    Period); 
  return E;
}
// Zorro version - Entropy indicator
// Method 2 - converting the DLL code to lite-C
var EntropyZ(vars Data,int Period)
{
  Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size 
  var sumx = 0., sumx2 = 0.;
  int j;
  for (j=0; j<Period; j++) {
    sumx += log(Data[j]/Data[j+1]);
    sumx2 += log(Data[j]/Data[j+1]) * log(Data[j]/Data[j+1]);
  }
  var avgx = sumx/Period;
  var rmsx = sqrt(sumx2/Period);
  var P = ((avgx/rmsx)+1)/2.;
  return P * log(1+rmsx) + (1-P) * log(1-rmsx);
}

 

MatLab™

MatLab is a commercial computing environment and interactive programming language by MathWorks, Inc. It has some similarity to R, but is not specialized on data analysis and machine learning. It allows symbolic and numerical computing in all fields of mathematics. With interpreted code and accordingly slow execution, it is not suited for backtesting trading strategies (unless they are converted to a vectorized structure, which is however an awkward process). Converting MatLab code to C is also a lot of work due to the very different language syntax. Fortunately there is a better solution: MatLab has an integrated compiler that compiles a MatLab trading algorithm to a C/C++ DLL. The DLL function can then be called from a lite-C script.

See also:

Introduction to lite-C, Pointers, Structs, Functions, DLLsMT4 bridge, R bridge, Python bridge, C++ to lite-C, lite-C to C++

► latest version online