Jak działa AFL

Wstęp

Jedną z najbardziej istotnych cech AFL jest fakt, iż jest to język przetwarzający tablice, a obliczenia prowadzone są na całych tablicach (wektorach) danych. Działanie programu można więc porównać do pracy popularnych arkuszy kalkulacyjnych (takich jak MS Excel). Każdy użytkownik, który zaznajomiony jest z pracą w arkuszu kalkulacyjnym nie powinien mieć żadnych problemów ze zrozumieniem idei działania AFL (zresztą wszystkie przykłady w tym rozdziale przygotowane zostały przy użyciu MS Excel).

Czym jest tablica?

Tablica to po prostu lista (rząd) wartości. W literaturze jest też często określana pojęciem wektor. W poniższym przykładzie - każdy numerowany rząd wartości jest oddzielną tablicą. W bazie danych progamu AmiBroker przechowywane jest 6 tablic dla każdego z symboli. Jedna dla cen otwarcia, druga dla najniższych cen, trzecia dla najwyższych, kolejne: dla cen zamknięcia, dla wolumenu obrotów oraz ostatnia dla liczby otwartych pozycji. Z poziomu AFL można odwoływać się do tych tablic przy użyciu zmiennych: Open, High, Low, Close, Volume i OpenInt, lub: O, H, L, C, V, OI.

Bar
1
2
3
4
5
6
7
8
9
10
1 Open
1,23
1,24
1,21
1,26
1,24
1,29
1,33
1,32
1,35
1,37

Przykład 1. Tablica cen otwarcia (Open).


Każda inna tablica danych obliczna jest (gdy jest to potrzebne) z tych 6 tablic dostępnych w bazie danych. Dodatkowe tablice nie są przechowywane w bazie danych, lecz są obliczane na bieżąco przy użyciu formuł języka AFL.

Każda wartość w tablicy jest przypisana do konkretnej daty. Gdy włączone jest narzędzie Dymek (Preferences -> Miscellaneous Tab - > Price data tool tips), po ustawieniu kursora nad wykresem cen, pojawi się małe, żółte pole zawierające ceny. AFL wyszukuje w bazie wartości O,H,L,C,V,OI w odpowiednich tablicach i wyświetla tą informację w dymku.

Przetwarzanie tablic - dlaczego AFL jest tak szybki?

Sprawdźmy jak obliczane jest następujące wyrażenie:

MyVariable = ( High + Low )/2;

Kiedy AFL oblicza wyrażenia takie jak ( High + Low )/2 nie ma potrzeby, by kod ten obliczać dla każdej daty oddzielnie. Zamiast tego AFL odczytuje całą tablicę cen High oraz całą tablicę cen Low i dodaje odpowiadające sobie elementy w jednym momencie. Innymi słowy, operator + (jak również i inne operatory) oblicza od razu całą tablicę i operacja jest wykonywana z prędkością porównywalną do skompilowanego kodu, następnie tablica zawierająca wynik dodawania jest dzialona przez 2 (również w jednym etapie).

Spójrzmy na szczegóły - patrz przykł. 2.. Kiedy AFL oblicza ( High + Low )/2 na początku pobiera tablice High (1) i Low (2) i (w jednym kroku kompilacji) jako wynik tworzy tymczasową tablicę (3). Następnie tworzy tablicę końcową (4) poprzez podzielenie każdego elementu tablicy (3) przez 2. Rezultat jest przypisany do zmiennej myVariable.

Bar
1
2
3
4
5
6
7
8
9
10
1 High (built-in array)
1,24
1,27
1,25
1,29
1,25
1,29
1,35
1,35
1,37
1,29
2 Low (built-in array)
1,20
1,21
1,19
1,20
1,21
1,24
1,30
1,28
1,31
1,27
3 High+Low (temporary array created during evaluation)
2,44
2,48
2,44
2,49
2,46
2,53
2,65
2,63
2,68
2,46
4 ( High+Low ) /2 (gets assigned to MyVariable)
1,22
1,24
1,22
1,245
1,23
1,265
1,325
1,315
1,34
1,23

Przykład 2. Kroki AFL przy obliczaniu wyrażenia ( High + Low ) /2

Średnie kroczące, wyrażenia warunkowe.

Rozważmy teraz następujący kod:

Cond1 = Close > MA( Close, 3 );
Cond2 = Volume > Ref( Volume, -1 );
Buy = Cond1 AND Cond2;
Sell = High > 1.30;

Ten kod generuje sygnał kupna kiedy dzisiejsza cena zamnknięcia jest wyższa niż 3-dniowa średnia krocząca oraz gdy wolumen obrotów jest wyższy niż wolumen z dnia poprzedniego. Sygnał sprzedaży generowany jest gdy najwyższa cena (High) jest wyższa niż 1.30.

Kiedy w formule trzeba sprawdzić, czy cena zamknięcia jest wyższa niż np. 3-dniowa średnia krocząca, AFL w pierwszym kroku odczyta tablicę cen zamknięcia (Close) tworząc dla analizowanego symbolu nową tablicę MA(Close,3) zawierającą wartości średniej kroczącej. Każda komórka nowej tablicy może być później porównana z cenami zamknięcia. W przykładzie tablica nazwana Cond1 tworzona jest właśnie w taki sposób. Każda komórka tablicy Cond1, dla której cena zamknięcia jest większa niż odpowiadająca jej wartość średniej kroczącej, przyjmuje wartość '1'. Natomiast gdy cena zamknięcia nie jest większa niż średnia krocząca, komórka tablicy Cond1 przyjmuje wartość '0'.

AFL może również odwoływać się do wcześniejszych lub późniejszych wartości tej samej tablicy przy użyciu funkcji Ref (patrz rząd 6 gdzie utworzona jest tymczasowa tablica przechowująca wartośći wolumenu z poprzedniego dnia).

W rzędzie 9 została stworzona nowa tablica, wynikająca z porównania każdej komórki tablicy wolumenu z wartością poprzednią wolumenu. Rezultat zapisany jest jako '1' dla komórek, dla których warunek Cond2 jest prawdziwy oraz '0' dla komórek, dla których warunek Cond2 jest fałszywy.

Rząd 10 zawiera tablicę o nazwie Buy (Kup) stworzoną przez porównanie wartości komórek tablic Cond1 i Cond2. Gdy zarówno wartość Cond1 jak i Cond2 równa jest '1' - wtedy tablica 'Buy' przyjmuje wartość '1'.

Rząd 11 pokazuje zawartość tablicy 'Sell' (Sprzedaj) przyjmującej wartość '1' dla każdej komórki, gdy wartość tablicy Close' (cen zamknięcia) większa jest od $1.30.

Day
1
2
3
4
5
6
7
8
9
10
1 Open
1,23
1,24
1,21
1,26
1,24
1,29
1,33
1,32
1,35
1,37
2 High
1,24
1,27
1,25
1,29
1,25
1,29
1,35
1,35
1,37
1,29
3 Low
1,20
1,21
1,19
1,20
1,21
1,24
1,30
1,28
1,31
1,27
4 Close
1,23
1,26
1,24
1,28
1,25
1,25
1,31
1,30
1,32
1,28
5 Volume
8310
3021
5325
2834
1432
5666
7847
555
6749
3456
6 Ref( Volume, -1 ) (tablica tymczasowa utworzona podczas obliczeń)
Null
8310
3021
5325
2834
1432
5666
7847
555
6749
7 MA( Close, 3 ) (tablica tymczasowa utworzona podczas obliczeń)
Null
Null
1,243
1,260
1,257
1,260
1,270
1,287
1,310
1,300
8 Cond1 = Close < MA(close,3) (daje 1 (lub 'Prawda') gdy warunek jest spełniony, w przeciwnym wypadku 0)
Null
Null
1
0
1
1
0
0
0
1
9 Cond2 = Volume > Ref(volume,-1)
Null
0
1
0
0
1
1
0
1
0
10 Buy = Cond1 AND Cond2
Null
Null
1
0
0
1
0
0
0
0
11 Sell = High > 1.30
0
0
0
0
0
0
1
1
1
0

Oczywiście Buy i Sell są specjalnymi tablicami, których wartości mogą być wyświetlone w oknie Analizy Automatycznej lub na ekranie przy użyciu kolorowanego tekstu.

Bardziej szczegółowo

Poniższe przykłady były bardzo proste. Zanim przejdziemy dalej, koniecznie jest wyjaśnienie następujących pojęć:

Tak jak napisano w rozdziale Samouczek: Operacje podstawowe możliwe jest zaznaczenie dowolnej daty na wkresie jak też zaznaczenie wybranego okresu. Wartość oznaczona pionową linią nazywana jest 'wartością wybraną' natomiast początek i koniec przedziału nazwywane będą: 'wartość początkowa' oraz 'wartość końcowa'. Język AFL posiada wbudowane funkcje pozwalające odwoływać się do tych danych. Są to odpowiednio SelectedValue, BeginValue oraz EndValue. Oprócz tych 3 funkcji jest jeszcze funkcja LastValue, która pozwala odwoływać się do ostatniej wartości tablicy. Wszystkie te funckje odczytują wartość danej tablicy w określonym punkcie i zwracają POJEDYNCZY NUMER oznaczający wartość tablicy dla danego punktu. Pozwala to na obliczanie pewnych statystyk w odniesieniu do wybranego punktu, czyli np.:

EndValue( Close ) - BeginValue( Close )

Zwróci wartość różnicy cen zamknięcia w zaznaczonym okresie.

Kiedy numer zwrócony przez jedną z wymienionych funkcji jest porównany do tablicy bądź zachodzi dowolne działanie uwzględniająe tablicę oraz numer, obliczenie przeproweadzone jest tak, jakby wszystkie elementy tablicy przyjmowały identyczną wartość. Zilustrowane jest to w poniższej tablicy (rzędy 2, 6, 7). Kolor zielony oznacza "wartość początkową" a kolor czerwony "wartość końcową". Wartość wybrana oznaczona jest na niebiesko.

Day
1
2
3
4
5
6
7
8
9
10
1 Open
1,23
1,24
1,21
1,26
1,24
1,29
1,33
1,32
1,35
1,37
2 BeginValue( Open )
1,24
1,24
1,24
1,24
1,24
1,24
1,24
1,24
1,24
1,24
3 EndValue( Open )
1,32
1,32
1,32
1,32
1,32
1,32
1,32
1,32
1,32
1,32
4 SelectedValue( Open )
1,21
1,21
1,21
1,21
1,21
1,21
1,21
1,21
1,21
1,21
5 LastValue( Open )
1,37
1,37
1,37
1,37
1,37
1,37
1,37
1,37
1,37
1,37
6 Close
1,22
1,26
1,23
1,28
1,25
1,25
1,31
1,30
1,32
1,28
7 Close <= BeginValue( Open )
1
0
1
0
0
0
0
0
0
0
8 result = IIF( Close <= BeginValue( Open ), Close, Open );
1,22
1,24
1,23
1,26
1,24
1,29
1,33
1,32
1,35
1,37
9 Period
2
3
4
2
3
5
2
3
4
2
10 Factor = 2/(Period+1)
0,667
0,500
0,400
0,667
0,500
0,333
0,667
0,500
0,400
0,667
11 1 - Factor
0,333
0,500
0,600
0,333
0,500
0,667
0,333
0,500
0,600
0,333
12 AMA( Close, Factor )
0,8125
1,0363
1,1138
1,2234
1,2367
1,2399
1,2853
1,2927
1,3036
1,2866

Teraz wyjaśnine zostanie działanie funkcji IIF(condition, truepart, falsepart). Działa ona w ten sposób, że zwraca wartość drugiego (truepart) albo trzeciego (falsepart) argumentu w zależności od wyrażenia condition. Jak można zauważyć w poniższej tablicy w rzędzie 8, przyjmuje on wartości z tablicy (Close) cen zamknięcia (truepart) dla punktów, w których warunek Cond1 jest spełniony (1), oraz z tablicy (Open) cen otwarcia (falsepart), dla punktów, w których warunek Cond1 nie zachodzi. Zatem tablica utworzona w wyniku działania funkcji IIF zawiera wartości zarówno z tablicy Open jak i Close. Należy zwrócić uwagę, iż oba warunki truepart and falsepart są tablicami i są wykonane bez względu na warunek (zatem nei jest to regularne wyrażenie IF-THEN-ELSE ale funkcja która zwraca TABLICĘ).

Funkcja AMA( array, factor) zdaje się sprawiać najwięcej problemów z jej zrozumieniem, choć w istocie jest bardzo prosta. Działa ona w sposób rekursywny, tzn. odwołując się do swoich poprzednich wartości przy obliczaniu wartości bieżącej. Przetwarza ona tablice punkt po punkcie, mnożąc w każdym etapie pierwszy argument (tablicę - array) przez drugi (współczynnik - factor) i dodając wynik do poprzednij wartości funkcji przemnożonej przez współczynnik róny (1-factor). Spójrzmy na 3 kolumnę. Wartość funkcji AMA w kolumnie 3 (1,23) obliczona jest z przemnożenia ceny Close z kolumny 3 przez współczynnik (factor) róny 0,4. Następnie dodajemy poprzednią wartość funckji AMA (1,0363) pomnożoną przez współczynnik (1-factor = 0,6). Rezultat (zaokrąglony do 4 miejsca po przecinku) równy jest 1,23 * 0,4 + 1,0363 * 0,6 = 1,1138.

Patrząc na wartości w rzędzie 12 można zauważyć, iż wyglądają one jak średnia krocząca cen zamknięcia. Jest to prawdą, gdyż właśnie pokazaliśmy jak obliczyć zmiennookresową wykładniczą średnią kroczącą przy użyciu funkcji AMA.

Użycie pętli

AmiBroker pozwala również na iterację przez notowania przy użyciu pętli for i while oraz pozwala na sterowanie wykonaniem formuły z wykorzystaniem wyrażenia if-else. Cechy te pozwalają na pracę na oba sposoby - wykorzystanie prędkości funkcji operujących na tablicach wraz z wykorzystaniem pętli do bardziej skomplikowanych obliczeń. Poniższy przykład pokazuje jak można zaimplementować zmiennookresową wykładniczą średnią kroczącą (opisaną poniżej), tym razem przy użyciu pętli.

Period = ... jakieś obliczenia

vaexp[ 0 ] = Close[ 0 ]; // inicjalizacja pierwszej wartośći

for( i = 1; i < BarCount; i++ )
{
  // obliczenie wartości wpółczynnika wygładzającego
  Factor = 2/(Period[ i ] + 1 );

  // oblicza wartość i-tego elementu tablicy
  // używa ceny zamknięcia z danego punktu ( close[ i ] ) i poprzedniej wartości średniej ( vaexp[ i - 1 ]
)
  vaexp[ i ] = Factor * Close[ i ] + ( 1 - Factor ) * vaexp[ i - 1 ];
}

Jak widać, kod jest dłuższy, ale z drugiej strony jest bardzo podobny do każdego innego języka progarmowania takich jak C/Pascal/Basic. Zatem użytkownicy z pewnym doświadczeniem w programowaniu mogą postrzegać pętle jako łatwiejsze w stosowaniu.

Jeśli jesteś początkującym użytkownikiem, zalecane jest rozpoczęcie nauki od funkcji przetwarzających tablice.

Jeśli masz kłopoty z programowaniem w AFL, pewnym ułatwieniem może okazać się obliczanie tablic w MS Excel. Jeśli to również sprawia początkowo problemy - nieoceniona może okazać się pomoc znajomego, szczególnie jeśli jest księgowym.

Gdy już jasne stanie się posługiwanie językiem AFL, możliwe będzie odtworzenie każdego systemu, bądź napisanie własnego.

--- Specjalne podziękowania dla Geoff'a Mulhall'a za oryginalny artykuł w newsletterze który był podstawą tego samouczka ---