Quantcast
Channel: AFL – AmiBroker Knowledge Base
Viewing all 56 articles
Browse latest View live

How to synchronize backtesting setup on different computers

$
0
0

When comparing the output of back-tests obtained from different working machines, it is necessary to make sure that all aspects of our testing are identical, including:

  1. the database
  2. the formula used for testing
  3. the settings

In order to synchronize data – the best is to copy the entire local database folder. Using just the same data source, especially if it is real-time feed may not be enough due to different array lengths or some corrections that may have been applied in historical data on data-vendors server in between.

In case of any differences in results between two computers that is the very fist thing to check, as different input would result in different output.

To find out that the data are different you may simply create a checksum of data columns, using code like shown below:

Filter Status("lastbarinrange");
AddColumnCumHigh Low Close Open ), "Price checksum");
AddColumnCumVolume ), "Volume checksum" );
AddColumnBarIndex(), "Number of bars");
AddSummaryRows); // add total sum of columns

By running this code on both computers you can compare checksums to see if they are the same.

In order to transfer the formula and settings to the other machine it is enough to:

  1. select Analysis window
  2. use File->Save from the main menu and save the APX file
  3. open the same APX file on the other computer

If data, code and settings are identical, then the obtained results will also stay in sync.


How to restrict trading to certain hours of the day

$
0
0

In order to include time-based conditions in the back-testing code – we can use TimeNum() function to check the time-stamp of given bar and use it as input for any time-based conditions.
http://www.amibroker.com/f?timenum

In order to code a strategy that triggers trades only in certain hours of the day, 9:30-11:00 in this example, we can use the following approach (code uses simple MACD crossovers to generate signals):

tn TimeNum();
startTime 93000// start in HHMMSS format
endTime 110000;  // end in HHMMSS format
timeOK tn >= startTime AND tn <= endTime;

Buy CrossMACD(), Signal() ) AND timeOK;
Sell CrossSignal(), MACD() ) AND timeOK

It is also possible to force an exit signal after 11:00 to avoid overnight positions:

tn TimeNum();
startTime 93000// start in HHMMSS format
endTime 110000;  // end in HHMMSS format
timeOK tn >= startTime AND tn <= endTime;

Buy CrossMACD(), Signal() ) AND timeOK;
Sell = (CrossSignal(), MACD() ) AND timeOK) OR CrosstnendTime ); 

New keywords in AFL and possible conflict with user-defined variables

$
0
0

AmiBroker 4.91.0 BETA introduced the following new keywords:

switch, case, break, continue, default

You have to make sure that your formulas do not use them as variable names. The above words are now reserved AFL keywords and if you use them for your own variables you need to replace this identifiers with names that do not conflict with the reserved keywords.

This article shows how to perform multiple-file text replace very quickly.

To quickly update all your AFL files you can use this freeware tool:

TextRep 2.0 from:

http://www.no-nonsense-software.com/cgi-bin/redirect/cgirdir.exe?texrep

More info:
http://www.no-nonsense-software.com/freeware/

Instructions:

1. Download from http://www.no-nonsense-software.com/cgi-bin/redirect/cgirdir.exe?texrep
and unzip

2. Run setup program (from inside zipped folder)

3. Run TextRep 2.0 program

4. Switch to “Scan folder(s)” page

5. Enter AmiBroker Formulas folder (by default it is C:\Program Files\AmiBroker\Formulas),
mark “Include subfolders” and enter *.afl into “File types” as shown in the picture

6. Enter “switch” in the “Text to find” box and “switchvar” in the “Replace with” field.
“Case sensitive” should remain UNchecked. (Texts should be entered without quotation marks)

Text Rep 2.0 screen

7. Press Start to go on with replacing

8. Repeat steps 6 and 7 with the following word pairs:
“case” -> “casevar”
“break” -> “breakvar”
“continue” -> “continuevar”
“default” -> “defaultvar”

9. Run AmiBroker 4.91 and check if everything works fine. If so, you may delete .bak (backup) files created during replace process.

QuickAFL facts

$
0
0

QuickAFL(tm) is a feature that allows faster AFL calculation under certain conditions. Initially (since 2003) it was available for indicators only, as of version 5.14+ it is available in Automatic Analysis too.

Initially the idea was to allow faster chart redraws through calculating AFL formula only for that part which is visible on the chart. In a similar manner, automatic analysis window can use subset of available quotations to calculate AFL, if selected “range” parameter is less than “All quotations”.

So, in short QuickAFL works so it calculates only part of the array that is currently visible (indicator) or within selected range (Automatic Analysis).

Your formulas, under QuickAFL, may or may NOT use all data bars available, but only visible (or “in-range”) bars (plus some extra to ensure calculation of used indicators), so when you are using Close[ 0 ] it represents not first bar in entire data set but first bar in array currently used (which is just a bit longer than visible, or ‘in-range’ area).

The QuickAFL is designed to be transparent, i.e. do not require any changes to the formulas you are using. To achieve this goal, AmiBroker in the first execution of given formula “learns” how many bars are really needed to calculate it correctly.

To find out the number of bars required to calculate formula AmiBroker internally uses two variables ‘backward ref’ and ‘forward ref’.

‘backward ref’ describes how many PREVIOUS bars are needed to calculate the value for today, and ‘forward ref’ tells how many FUTURE bars are needed to calculate value for today.

If these numbers are known, during execution of given formula AmiBroker takes FIRST visible (or in-range) bar and subtracts ‘backward ref” and takes LAST visible (or in-range) bar and adds ‘forward ref’ to calculate first and last bar needed for calculation of the formula.

Now, how does AmiBroker know a correct “backward ref” and “forward ref” for the entire formula?
Well, every AmiBroker’s built-in function is written so that it knows its own requirements and adds them to global “backward ref” and “forward ref” each time given function is called from your formula.

The whole process starts with setting initial BackwardRef to 30 and ForwardRef to zero. These initial values are used to give “safety margin” for simple loops/scripts.

Next, when parser scans the formula like this:

Buy Ref MAC40 ), -);

it analyses it and “sees” the MA with parameter 40. It knows that simple moving average of period 40 requires 40 past bars and zero future bars to calculate correctly so it does the following (all internally):

BackwardRef BackwardRef 40;
ForwardRef ForwardRef 0;

So now, the value of BackwardRef will be 70 (40+30(initial)), and ForwardRef will be zero.

Next the parser sees Ref( .., -1 );

It knows that Ref with shift of -1 requires 1 past bar and zero future bars so it “accumulates” requirements this way:

BackwardRef BackwardRef 1;
ForwardRef ForwardRef 0;

So it ends up with:

BackwardRef 71;
ForwardRef 0;

The BackwardRef and ForwardRef numbers are displayed by AFL Editor’s Tools->Check and Profile as well as on charts when “Display chart timing” is selected in the preferences.

If you use Check and Profile tool, it will tell you that the formula

Buy Ref MAC40 ), -);

requires 71 past bars and 0 future bars.

You can modify it to

Buy Ref MAC50 ), -);

and it will tell you that it requires 82 past bars (30+50+2) and zero future bars.

If you modify it to

Buy Ref MAC50 ), );

It will tell you that it needs 80 past bars (30+50) and ONE future bar (from Ref).

Thanks to that your formula will use 80 bars prior to first visible (or in-range) bar leading to correct calculation result, while improving the speed of execution by not using bars preceding required ones.

IMPORTANT NOTES

It is very important to understand, that the above estimate requirements while fairly conservative,
and working fine in majority of cases, may NOT give you identical results with QuickAFL enabled, if your formulas use:
a) JScript/VBScript scripting
b) for/while/do loops using more than 30 past bars
c) any functions from external indicator DLLs
d) certain functions that use recursive calculation such as very long exponential averages or TimeFrame functions with much higher intervals than base interval

In these cases, you may need to use SetBarsRequired() function to set initial requirements to value higher than default 30. For example, by placing

SetBarsRequired1000);

at the TOP of your formula you are telling AmiBroker to add 1000 bars PRIOR to first visible (or in-range) bar to ensure more data to stabilise indicators.

You can also effectively turn OFF QuickAFL by adding:

SetBarsRequiredsbrAllsbrAll );

at the top of your formula. It tells AmiBroker to use ALL bars all the time.

It is also worth noting that certain functions like cumulative sum (Cum()) by default request ALL past bars to guarantee the same results when QuickAFL is enabled. But when using such a function, you may or may NOT want to use all bars. So SetBarsRequired() gives you also ability to DECREASE the requirements of formula. This is done by placing SetBarsRequired at the END of your formula, as any call to SetBarsRequired effectively overwrites previously calculated estimate. So
if you write

Cum);
SetBarsRequired1000); // use 1000 past bars DESPITE using Cum()

You may force AmiBroker to use only 1000 bars prior first visible even though Cum() by itself would require all bars.

It is also worth noting that when QuickAFL is used, BarIndex() function does NOT represent elements of the AFL array, but rather the indexes of ENTIRE quotation array. With QuickAFL turned on, an AFL array is usually shorter than quotation array, as illustrated in this picture:

QuickAFL, BarIndex and BarCount

SPECIAL CASE: AddToComposite function

Since AddToComposite creates artificial stock data it is desirable that it works the same regardless of how many ‘visible’ bars there are or how many bars are needed by other parts of the formula.

For this reason internally AddToComposite does this:

SetBarsRequiredsbrAllsbrAll );

which effectivelly means “use all available bars” for the formula. AddToComposite function simply tells the AFL engine to use all available bars (from the very first to the very last) regardless of how formula looks like. This is to ensure that AddToComposite updates ALL bars of the composite

The side-effect is that “Check And Profile” feature will see that it needs to reference future bars and display a warning even though this is false alert because AddToComposite itself has no impact on trading system at all.

Now why this shows only when flag atcFlagEnableInBacktest is on ??
It is simple: this is so because it means that AddToComposite is ACTIVE in BACKTEST.
http://www.amibroker.com/guide/afl/afl_view.php?name=ADDTOCOMPOSITE

Since “Check And Profile” uses “BACKTEST” state you get such result.

If atcFlagEnableInBacktest is not specified AddToComposite is not enabled in Backtest and hence does not affect calculation of BackwardRef and ForwardRef during “Check And Profile”.

BACKWARD COMPATIBILITY NOTES

a) QuickAFL is available in Automatic Analysis in version 5.14.0 or higher
b) sbrAll constant is available in Automatic Analysis in version 5.14.0 or higher. If you are using older versions you should use numeric constant of: 1000000 instead.

AFL execution speed

$
0
0

NOTE: Benchmarks and timings given below are little outdated because article was written back in 2008. Today’s CPUs are faster and memory bandwidth is also higher.

AmiBroker Formula Language (AFL) thanks to its array processing model is able to run at the same speed as code written in assembler (i.e. machine code). The following article explains how.

AFL runs with native assembly speed when using array operations.
A simple array multiplication like this:

Close  H// array multiplication (each array element is multiplied)

gets compiled by AmiBroker to just 8 assembly instructions:

loop:
mov edx,dword ptr [esp+58h]
inc esi ; increase counters
add eax,4
cmp esi,edi
fld dword ptr [edx+esi*4-4] ; get element of close array
fmul dword ptr [eax+ecx-4] ; multiply by element of high array
fstp dword ptr [eax-4] ; store result
jl loop ; continue until all elements are processed

As you can see there are three 4 byte memory accesses per loop iteration (2 reads each 4 bytes long and 1 write 4 byte long)

With such tight loop, single processor core running an AFL formula is able to saturate memory bandwidth in majority of most common operations/functions if total array sizes used in given formula exceedes DATA cache size.

On my (2 year old) 2GHz Athlon x2 64 single iteration of this loop takes 6 nanoseconds (see benchmark code below). 6 nanoseconds to process one bar of data, or 166 million bars per second. So, during 6 nanoseconds we have 8 byte reads and 4 byte store. Thats (8/(6e-9)) bytes per second = 1333 MB per second read and 667 MB per second write simultaneously i.e. 2GB/sec combined !

Now if you look at memory benchmarks you will see that 2GB/s is the limit of system memory speed on Athlon x64 (DDR2 dual channel)
And that’s considering the fact that Athlon has superior-to-intel on-die integrated memory controller (hypertransfer)

// benchmark code
// for accurrate results run it on LARGE arrays -
// intraday database, 1-minute interval, 50K bars or more)
GetPerformanceCounter(1); 
for(01000k++ )  H"Time per single iteration [s]="+1e-3*GetPerformanceCounter()/(1000*BarCount); 

Only really complex operations that use *lots* of FPU (floating point) cycles such as trigonometric (sin/cos/tan) functions are slow enough for the memory to keep up.

About floating point arithmetic

$
0
0

In general, to represent numbers with fractional parts, computers use a “floating point” binary representation. Floating point arithmetic is also used by AmiBroker for AFL calculations. For some more information about floating point representation in general see the following article, here we will only discuss some practical aspects.

http://en.wikipedia.org/wiki/Floating_point

Floating point calculations are performed in hardware by FPU (Floating Point Unit), which today is a part of your computer’s processor (CPU). The calculations are performed according to IEEE754 standard (see below) that all CPU manufacturers follow.

Internally in computers all numbers are represented in binary system.
This fact has some important consequences in practice. One of it is that some fractions that have finite representation in decimal system are not finite in binary.

For example, 0.1 is endless (infinite) fraction in binary system, as 1/3 or 2/3 fractions are in decimal system.

The mantissa of 0.1 fraction in binary system is cyclical:
1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 (1001)* …….

* represents endless cycle.

Since computers operate on limited word length, 32 bit binary representation of 0.1 is:
01111011 10011001100110011001100 (not rounded) (in decimal it is 0.09999999403953552)
or
01111011 10011001100110011001101 (rounded) (in decimal it is 0.10000000149011612)

Later is used by FPU (floating point processor) in your CPU because it has smaller relative error. If you add it nine times you will end up with 0.9000000134110449 which is of course higher than 0.9.

It will be easier for you to understand when explaining on decimal numbers. For example 2/3 represented in decimal is:
0.666666667

Now add THREE times this number (0.666666667 + 0.666666667 + 0.666666667) and what you will get?
2.000000001 and that is greater than 2.

Therefore, it is rule in programming, NEVER use fractions for loop counters. For that reason you should not use fractions in Optimize(), or if you need to use them use them in WISE way by adding HALF of “step” value to the “max” value.

step = 0.1;
x = Optimize(“x”, 0.5, 0.1, 0.9 + step/2, step);

Another thing to keep in mind is that 32-bit floating point number has only 7 significant digits (those digits that carry meaning contributing to its precision). So in 123.4567 all digits are significant and accurate, but in 123.456789, last two digits (‘8′ and ‘9’) are not significant and subject to floating point rounding – see links below).

See also:
About significant figures:
http://en.wikipedia.org/wiki/Significant_figures

IEEE754 conversion calculators:
http://babbage.cs.qc.edu/IEEE-754/Decimal.html
http://babbage.cs.qc.edu/IEEE-754/32bit.html

IEEE754 standard description:
http://en.wikipedia.org/wiki/IEEE_754-1985

Essay about comparing floatin point numbers:
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

Microsoft Knowledge Base: “Precision and Accuracy in Floating-Point Calculations”
http://support.microsoft.com/kb/125056

Using 32-bit floating point (as opposed to 64-bit double precision) has two main advantages:
a) consumes HALF of memory required for doubles (this *is* important, more important that you think, because if you have for example an array of 500000 elements, in floats it is 2MB and it fits into CPU cache, while in doubles it would be 4MB and may not fit into CPU cache).
b) 32-bit floating point numbers can be computed much faster. It simply takes less processor cycles to compute 32-bit float than 64-bit float. 64-bit version of AmiBroker is going even further and is using SSE2 instructions. This means that vectors of 4 single-precision numbers are processed in parallel using single SSE2 instruction on SINGLE processor. This gives more speed on single core than achievable using multiple cores.

Note also AmiBroker does use double precision (64bit) where it is necessary (for certain internal calculations)

A function with multiple return values

$
0
0

A typical AFL function returns one value. For example sin( x ) returns sine value of argument x. Sometimes however it is useful and/or required to return more than one value from the function.

Returning multiple values is possible only via arguments passed by reference, but trouble is that in AFL all arguments are passed by value (as in C language). Passing by value means that only value of variable is passed, not the variable itself, so original variable is not modified as shown in the example below:

function Dummy)
{
    7// x is treated as function-local
}
//
val 10;
Dummyval );
printf"%g\\n"val ); // will print 10 because 'val' is unaffected by function call

The behaviour shown above is desirable because we usually want the function to be opaque and do not interfere with what is defined outside of the function except for returning the result of the function.

But what if we actually wanted to write to variables passed as arguments? Well that is possible if we pass the names of the variables as arguments.

// This example shows how to return multiple values
// the idea is to pass the name of the variable instead of
// value
//
function fun_multiple_resultsresult1nameresult2name )
{
  VarSetresult1name); // setting variable using passed name
  VarSetresult2name);
  return;
}
//
// to get multiple values from a function
// we call the function passing NAMES of variables
//
10;
20;
printf("a = %g\\n");
printf("b = %g\\n");
//
fun_multiple_results"a""b" ); // pass the names of variables
//
printf("a = %g\\n"); // see new values assigned to variables
printf("b = %g\\n");

Of course we can use arguments passed by name for two-way communication – we can use them as both inputs and outputs as shown in the following example that swaps the values of arguments

function Swapvar1namevar2name )
{
    temp1 VarGetvar1name ); // read the value from variable 
    temp2 VarGetvar2name );
    VarSetvar1nametemp2 ); // write the value to variable
    VarSetvar2nametemp1 );
}
// Initial values
5;
37;
//
printf("Before swap x = %g, y = %g\\n"x);
//
Swap"x""y" ); // pass names of variables
//
printf("After swap x = %g, y = %g\\n"x);

The code above will produce output like this:

Before swap x = 5, y = 37
After swap x = 37, y = 5

So it is clear that variables were passed to the function, swapped and returned successfully.

Do NOT make assumptions on number of bars

$
0
0

From time to time some users face “Error 10. Subscript out of range” in their formulas. The error itself is described in the manual, but still a few words of explanation why it happens may be useful.

The error usually occurs when formula uses hard-coded number of bars in the loop statements like this:

for( 0300i++ ) // MISTAKE: FIXED number of bars
{
  Close]; // ERROR 10. because 'i' becomes greater than BarCount
}

or like this:

for( BarCount 1BarCount 300i-- ) // MISTAKE: FIXED number of bars
{
  Close]; // ERROR 10. because 'i' becomes LESS than zero
}

In both cases the code will FAIL if it is run on symbol that has LESS than 300 bars (BarCount < 300). In fact it will even fail on symbol that has more than 300 bars because of two facts:

  1. during AFL Editor’s Verify Syntax not more than 200 most recent bars are used
  2. a chart may be zoomed in so number of visible bars may be much lower and QuickAFL kicks in (so your AFL is executed with visible bars only).

This all means that one should never make any assumptions on number of bars your formula would get, because if you do, the formula will fail.

The formula should be written so it is able to execute without errors with BarCount as small as 1 (ONE).

This is normally done by writing a ‘for’ loop in a way recommended in the manual:

for( 0BarCounti++ )
{
   Close]; // this will never produce Error 10 because i is in the range 0..BarCount-1
}

If your formula references past data, say 10-bars earlier, you should start your loop with index 10, as below:

for( 10BarCounti++ )
{
  x] = Close] - Close10 ]; // both subscripts will be OK
}

What to do if your formula, for some reason, really requires fixed number of bars? Well, the answer is that you should check if you really get as many bars as you think:

if( BarCount 300 // check first if you have enough bars
{
   // here we know that we have more than 300 bars
   for( 0300i++ )
   {
      Close]; 
   }
}

Can I encrypt my formula so no-one can decipher it?

$
0
0

Currently the only way to protect your code from other peoples’ eyes is to translate part of the formula (such as few functions) or entire formula to C/C++ language and compile as AFL plugin DLL. Doing so requires some C/C++ programming knowledge, a C/C++ compiler (free Visual Studio Express or GNU tools can be used) and AmiBroker Development Kit (ADK).

ADK contains instructions how to write your own AFL plugin along with code samples that are ready-to-compile. Some C/C++ knowledge is required though.

ADK is freely downloadable from
http://www.amibroker.com/bin/ADK.exe (self-extracing exe)
or
http://www.amibroker.com/bin/ADK.zip (zip archive)

NOTE: ADK is not for the beginners. It is for programmers (i.e. for people who already wrote some C/C++ code in their life). We are working on providing alternative methods for non-programmers.

What are constants in AFL and how they work

$
0
0

The AFL language contains many pre-defined words like: shapeUpArrow, stopTypeTrailing, colorRed, styleThick, inDaily and many more. These are examples of constants. As written in AFL language specification (http://www.amibroker.com/guide/a_language.html): Constants are tokens representing fixed numeric or character values.

To better explain what this means, let us consider example of PI constant, which equals 3.14159265358979….. PI is the name of constant we use this name in mathematical equations, because it is easier and more practical to use than using the numerical value each time. Constants in AFL serve the same purpose, each of these words represents certain value properly interpreted by the program in the context they are used.

That is why using the following statement in backtesting code:

ApplyStopstopTypeTrailingstopModePercent10 );

is much better to use than cryptic statement like:

ApplyStop2110 );

Both commands are equivalent, because value of stopTypeTrailing constant equals 2 and value of stopModePercent constant equals 1, yet the first version is much more understandable.

There is also another reason to use pre-defined constants rather than hard-coded numbers in the code. If for any reason the internal value of given constant changes due to development needs – all formulas using constants will continue to work properly (because new version would interpret them properly), while hard-coded numbers may change the code execution. For example – inWeekly and inMonthly constants have changed with introduction of N*inDaily timeframes, however if we always used:
TimeFrameSet( in Weekly ); in the code, then such internal change does not really affect our formulas at all.

There is one more example worth discussing – in the documentation of PlotShapes function we can find:

PlotClose"Price"colorBlackstyleCandle );
//
Buy CrossMACD(), Signal() );
Sell CrossSignal(), MACD() );
//
shape Buy shapeUpArrow Sell shapeDownArrow;
//
PlotShapesshapeIIfBuycolorGreencolorRed ), 0IIfBuyLowHigh ) );

So – what does the multiplication mean in the above context? If we remember that constants are in fact just numbers, and boolean True in AFL has numeric value of 1, while boolean False has numeric value of 0, then:

– if Buy is True (equals 1) and Sell is False (equals 0), then the result of such calculation will be

shape = 1 * shapeUpArrow + 0 * shapeDownArrow = shapeUpArrow

– if Buy is False (equals 0) and Sell is True (equals 1), then the result of such calculation will be:

shape = 0 * shapeUpArrow + 1 * shapeDownArrow = shapeDownArrow

The above approach is kind of shortcut that saves using conditional statements. It would work correctly only if Buy and Sell signals never occur on the same bar and only if we assign just 0 or 1 (False / True) to Buy and Sell arrays. Otherwise the result of calculations would be different. The internal value of shapeUpArrow is 1 and ShapeDownArrow is 2, so in situation, where both Buy and Sell signals were true, we would get

shape = 1 * shapeUpArrow + 1 * shapeDownArrow = shapeUpArrow + shapeDownArrow = 1 + 2 = 3

So – we would then pass number 3 to PlotShapes function and this is neither shapeUpArrow nor ShapeDownArrow, but a different shape. That is why in general case it is better to use conditional function IIf, like shown below:

shape IIfBuyshapeUpArrowIIfSellshapeDownArrowshapeNone ) ); 

This way we are always sure that returned value will be shapeUpArrow or shapeDownArrow or shapeNone.

When and how often AFL code is executed?

$
0
0

All analysis in AmiBroker including charting, Analysis window or commentaries is based on underlying AFL code, which is being executed by the program to produce the required output. Therefore – any changes we see in the charts or analysis results (for example – chart updated with new ticks) mean that the program has received some input, then based on this information has recalculated the formula and presented the updated results.

These refreshes / formula recalculations depend on several factors:

  1. In a local database, which is not updated by a realtime plugin, the formula would get refreshed if we perform any actions, such as clicking, scrolling, zooming, changing parameters, choosing another symbol or interval, importing new data etc.
  2. In addition to the actions listed in (1), if we are running a plugin-based realtime feed, then the chart is refreshed based on “Intraday refresh interval” defined in Tools –> Preferences –> Intraday. If we enter 0 into this field, then it will result in chart being refreshed with every new tick (up to 10-times per sec).
  3. It is also possible to force certain refresh rate using dedicated RequestTimedRefresh() function in the AFL code: http://www.amibroker.com/f?RequestTimedRefresh
  4. It is also possible to manually refresh chart (or all chart and windows) using View->Refresh (or View->Refresh All) menu.

It is worth noting that chart formulas are refreshed only when they are placed on the active chart sheets. Non-active sheets just ‘don’t exist’, they are only created when you click on a bottom tab (sheet tab) to make them visible and destroyed immediately when other sheet becomes active. This ensures that precious CPU resources are not wasted on invisible chart sheets.

Additionally – by default charts in minimized chart windows or when multiple MDI windows are open and one is maximized, the windows in background that are completely obscured by others and/or minimized windows are not redrawn/refreshed during normal RT refresh. We can however call RequestTimedRefresh function with onlyvisible argument set to False and that will force regular refreshes in such windows as well.

RequestTimedRefreshFalse ); // force refresh for minimized MDI window or obscured MDI window

With regards to Analysis window – in general the formula is executed when we run e.g. Scan, Exploration, Backtest etc. Analysis window executes the formulas in multiple threads running in parallel (this tutorial explains multi-threading aspects: http://www.amibroker.com/guide/h_multithreading.html).

Repeated execution (to keep the code running over and over) in Analysis window can be also enabled with “Auto-repeat” option, the following Knowledge Base article explains it in details:

http://www.amibroker.com/kb/2014/10/03/how-to-setup-perodic-scans/

Last but definitely not least, we need to remember that AmiBroker may and will perform some executions internally for its own purposes such as:

  1. during AFL Syntax Check that happens when applying the chart, or sending the code to Analysis window, or updating existing formula
  2. when it is about to display Parameters window for the first time for given chart or during Parameters’ “Reset All” operation
  3. at the beginning of Optimization when it reads Optimize() statements to configure the optimization process and/or smart optimization engines
  4. at the beginning of each In-sample walk-forward step again to setup optimization parameters

Bottom line: we should never assume that certain formula will only be executed e.g. N-times during certain time-frame, because all really depends on the above factors, our actions and changing input.

Checking relationship between multiple moving averages

$
0
0

When we compare the positions of several lines against one another, we need to remember about using correct operators, so our AFL statement return correct results. Let us consider a set of moving averages using 10, 20, 30 and 40 periods, drawn with the following code:

MA10 MAClose10 );
MA20 MAClose20 );
MA30 MAClose30 );
MA40 MAClose40 );

PlotClose"Close"colorDefaultstyleBar );
PlotMA10"MA10"colorRed );
PlotMA20"MA10"colorBlue );
PlotMA30"MA10"colorGreen );
PlotMA40"MA10"colorgrey40 );

Multiple Moving averages

When we want to specify a condition where MA10 is highest of all of the lines, above MA20, which is above MA30 and with MA40 on the very bottom – we can NOT simply write:

condition MA10 MA20 MA30 MA40// WRONG - NOT what we really want, but no syntax error

It may seem strange that such statement is accepted without an error but it is actually syntactically correct. This is because of the fact that True and False are represented by numbers 1 and 0 respectively, so all comparisons actually have numerical value that allows such statement to be evaluated and yield numeric result. The above statement is evaluated from left to right and would be an equivalent of:

condition = ( ( MA10 MA20 ) > MA30 ) > MA40// again WRONG

Using > operator will return an array of True or False values (1 or 0). Therefore – the result of MA10 > MA20 comparison (which is True, that is equal to 1) would be then compared to MA30, resulting in checking 1 > MA30, then if such condition returns False (i.e. 0 ), we would end up with 0 > MA40 comparison that would return False (0) as the final output. This is of course not what we want to get.

That is why we should use AND operator instead, because we want to check several conditions being met at the same time, that is:

MA10 is above MA20
AND // must use AND/OR to combine multiple conditions
MA20 is above MA30
AND // must use AND/OR to combine multiple conditions
MA30 is above MA40

Therefore, we should write the AFL statement the following way:

condition MA10 MA20 AND MA20 MA30 AND MA30 MA40// correct way of checking multiple conditions

So as a general guideline – if you have multiple boolean (yes/no) conditions that you want to combine into single rule, you need to use AND operator between conditions if you want True result when all conditions are met at the same time.

In a similar way, if you want a True result when any one (or more) of multiple conditions is met, then you need to use OR operator.

condition MA10 MA20 OR MA20 MA30// this will give True result if any one condition (or both) is met

How to populate Matrix from a text file

$
0
0

AmiBroker 6.00 has introduced support for matrices. After we create a matrics with Matrix function call:

my_var_name Matrixrowscolsinitvalue);

then in order to access matrix elements, we need to use:

my_var_namerow ][ col ];

However – if we want to populate a relatively large matrix with values generated in other programs, then it may not be very practical to do it by hand in the AFL code assigning individual elements like this:

A][ ] = 1A][ ] = 4A][ ] = 6;

What we can do in such case is to store the values in a text file that we could use as input, then read through the file using fgets function and populate Matrix elements using a looping code. A sample formula showing how to perform such task is presented below.

A sample text file for this example can be found here: http://www.amibroker.com/kb/wp-content/uploads/2015/10/samplematrix.txt

// the input file path
file "C:\\samplematrix.txt";

// define the size of the desired matrix
rows 16;
cols 16;

// create matrix
myMatrix Matrixrowscols);

// open file
fh fopenfile"r" );

if( fh )
{
    0;

    // iterate through the lines of input file
    for( 0; ! feoffh ) AND rowsi++ ) 
    {
        // read a line of text
        line fgetsfh ); 

        if( line == "" )
        {
            Error("Too few rows in the data file or an empty row found");
            break;
        }
    
        // iterate through the elements of the line
        for( 0; ( item StrExtractline) ) != "" AND colsj++ ) 
        {
            // assign matrix element
            myMatrix][ ] = StrToNumitem );
        }
        
        if( cols )
        {
            Error("Too few columns in data file");
            break;
        }
    }
    
    fclosefh );
}
else
{
    Error"ERROR: file can not be opened" );
}

// spot check selected element
Title "spot check M[ 2 ][ 3 ]: " NumToStrMyMatrix][ ] );

How to fix Error 61 in printf/StrFormat calls

$
0
0

AmiBroker version 6.07 has introduced a strict check for correct string formatting in printf and StrFormat functions. These functions allow to specify the string followed by the list of arguments that will be inserted into the string in places, where %f, %g or %e specifiers are entered.

This works the following way:

StrFormat example

It is important for the list of subsequent arguments to match the number of % specifiers in the string. In cases, where there is no match – AmiBroker will display Error 61 message. Strict check is required because Microsoft C runtime crashes if wrong parameters are passed. Passing on earlier version was dangerous because it would lead to random crashes now and then depending on machine configuration.

There may be the following typical problems in the code:

Example 1. Four % specifiers, five value arguments

In this example formatting string contains four % specifiers so AmiBroker expects four arguments coming later, but five are given instead (too many).

// WRONG - too many value arguments
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g"OpenHighLowCloseVolume );

// CORRECT
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g"OpenHighLowClose );

Example 2. Five % specifiers, four value arguments

In this example formatting string contains five % specifiers so AmiBroker expects five arguments coming later, but four are given instead (too few).

// WRONG - %.0f specifier does not have a matching argument
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g, Volume: %.0f"OpenHighLowClose );

// CORRECT
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g, Volume: %.0f"OpenHighLowCloseVolume );

Example 3. Incorrectly coded percent (%) sign

In this example user wanted to print just % (percent sign), but used % (wrong) instead of %% (correct specifier of literal percent sign).

// WRONG - to show the % sign in the output we need to use %%
Title StrFormat"Close: %g (%.1f%)"CloseSelectedValueROCClose1) ) );

// CORRECT - you should use %% to print actual percent sign
Title StrFormat"Close: %g (%.1f%%)"CloseSelectedValueROCClose1) ) );

The example 3 requires special attention, as it is a common mistake. Due to the fact that % sign is a special character, we need to use %% in our string if we want to print % sign in the output string.

How to count symbols in given category

$
0
0

When we want to find out how many symbols belong to given category (such as watchlist) then for manual inspection, it is enough to hover the mouse cursor over the particular category name in the Symbols window and the information will be shown in a tooltip:

Category symbol count

If we want to check such information using AFL code, we could read the list of symbols returned with CategoryGetSymbols and by counting commas (which separate symbol names) find out the number of tickers.

A reusable function is presented below:

function CategoryCountSymbolscategorynumber )
{
   count StrCount( list = CategoryGetSymbolscategorynumber ), ",");  
   return IIf( list == ""0count );
}    

Title "Symbols in watchlist 0: " CategoryCountSymbolscategoryWatchlist);

Calling custom user functions in our code

$
0
0

AFL language allows us to define reusable functions that can be used in our formulas. The following chapter of the manual explains the procedure in details: http://amibroker.com/guide/a_userfunctions.html

When we want to call such function in our formula, we should add function definition into our code, so AmiBroker could identify and interpret custom keyword properly. Consequently, if we use the function in multiple chart panes, each of the formulas should contain the function definition first.

// custom function definition
function myMACD( array, fastslow )
{
   return EMA( array, fast ) - EMA( array, slow );
}

// use of function
PlotmyMACDHigh1226 ), "Open MACD"colorRed );

Since we may potentially define a large group of our own functions, pasting the definitions manually may not be very convenient. To avoid that, we can use #include statement and group our definitions in a separate AFL file which will be called with a single statement from our main code.

To create such file we should do the following:

  1. Create a new formula. The preferred location is in Include folder in chart windows, we can in fact choose any custom location of the file.
    include
  2. We can also rename the file to a descriptive name, for example myfunctions.afl:
    include
  3. Now we can edit the file and paste our function definitions, then save the file:
    function myMACD( array, fastslow )
    {
       return EMA( array, fast ) - EMA( array, slow );
    }
  4. Now in our main file we can use only a reference to myfunctions.afl file:
    // include our definitions
    #include <myfunctions.afl>
    
    // use of function
    PlotmyMACDHigh1226 ), "Open MACD"colorRed )

We don’t have to specify the path, because we saved our formula in the folder, which is specified as a ‘default include path’ in Tools–>Preferences–>AFL:

include

In other cases we should provide full path to the file – #include is a pre-processor command, therefore this time we use single backslashes in the path:

#include “C:\Program Files\AmiBroker\AFL\common.afl”

More information about include command can be found at:

http://www.amibroker.com/guide/afl/_include.html

Viewing all 56 articles
Browse latest View live