Generic indicator structure optimization:
1) String comparison in Update() function. You know that Update() is called every time when a new tick comes to the live market snapshot. So, we can recognize the Update function as called inside a loop. Now, the whole Update() function is just a big sequence of string comparisons to choose the proper function for the Method chosen during initialization. But the method does NOT change. Why do we need make that selection all the time? The better way is to store the proper function once, in the Prepare() method into the global variable and then call it.
was:
- Code: Select all
function Update(period, mode)
local i;
if (period>=first) then
if Method=="MVA" then
MA[period]=MVA(source,period,MA,Period);
elseif Method=="EMA" then
MA[period]=EMA(source,period,MA,Period);
...
now:
- Code: Select all
local UpdateFunction;
function Prepare(onlyName)
if Method=="MVA" then
UpdateFunction=_G["MVA"];
elseif Method=="EMA" then
UpdateFunction=_G["EMA"];
...
end
function Update(period, mode)
local i;
if (period>=first) then
UpdateFunction(source,period,MA,Period);
...
2) Needless internal streams. The indicator always creates five internal stream, even these streams aren't required at all. Handling of every, even unused, stream takes time on every tick. So, just create the streams ONLY in case they are actually required.
Algorithm optimization:
1) A lot of algorithms, such as simple moving average, linear-weighted moving average, least-square moving average are implemented in core in the mathex table. This table is implemented in C++, so it is faster than Lua.
Was:
- Code: Select all
local Weight=0;
local Sum=0;
local i;
for i=0,Period_-1,1 do
Weight=Weight+(Period_-i);
Sum=Sum+Price[shift-i]*(Period_-i);
end
if Weight>0 then
return Sum/Weight;
else
return 0;
end
else
return 0;
end
now
- Code: Select all
return mathex.lwma(Price, Period - Period_ + 1, Period);
Also, it is a good idea to understand the formula beneath the somebody's source first, because a part of the formula may be optimized. For example, for ILRS the slope may be calculate using mathex:
was:
- Code: Select all
function ILRS(Price,shift,MAarray,Period_)
local sum=Period_*(Period_-1)*0.5;
local sum2=(Period_-1)*Period_*(2*Period_-1)/6;
local sum1=0;
local sumy=0;
local i;
for i=0,Period_-1,1 do
sum1=sum1+i*Price[shift-i];
sumy=sumy+Price[shift-i];
end
local num1=Period_*sum1-sum*sumy;
local num2=sum*sum-Period_*sum2;
local slope;
if num2~=0 then
slope=num1/num2;
else
slope=0;
end
return slope+MVA(Price,shift,MAarray,Period_);
end
now:
- Code: Select all
params.buffer[period] = mathex.lregSlope(params.source, from, period) + mathex.avg(params.source, from, period);
2) Needless calculation
Even if algorithm is not implemented in full or partially in the mathex, there is no need to do repeating calculations which have exactly the same result for every tick. For example weights for SineWMA are the same and depends on the offset to the last bar only.
- Code: Select all
function SineWMA(Price,shift,MAarray,Period_)
if shift>=first then
local Sum=0;
local Weight=0;
local i;
r=core.range(math.max(shift-Period_+1,first),shift);
for i=r.from,shift,1 do
Weight=Weight+math.sin(math.pi*(shift-i+1)/(Period_+1));
Sum=Sum+Price[i]*math.sin(math.pi*(shift-i+1)/(Period_+1));
end
if Weight>0 then
return Sum/Weight;
else
return 0;
end
end
end
now
- Code: Select all
--
-- SineWMA: Sine weighted moving average
--
function SineWMAInit(source, n)
local p = {};
p.source = source;
p.n = n;
p.offset = n - 1;
p.sine = {};
p.first = source:first() + n - 1;
local i, w;
w = 0;
for i = 1, n, 1 do
p.sine[i] = math.sin(math.pi * (n - i + 1) / (n + 1));
w = w + p.sine[i];
end
p.weight = w;
p.alwaysZero = (w == 0);
return p;
end
function SineWMAUpdate(params, period, mode)
local sum = 0;
if not params.alwaysZero then
local src = params.source;
local sine = params.sine;
local n = params.n;
local p = period - n;
for i = 1, n, 1 do
sum = sum + src[p + i] * sine[i];
end
sum = sum / params.weight;
end
params.buffer[period] = sum;
end
So, as you can see all things are pretty simple, but provides a good performance improvements. When we want to use our indicators in 1-year long backtesting, and, especially, optimization, every additional second counts. 6 times optimization of the indicator may make 10-minute backtesting just a 1.5 minute long. A few hours of indicator optimization worths it, right?