Tổng hợp một số kiến thức lập trình về Amibroker

1 Giới thiệu khái quát

Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language  sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”.
Bản thân ngôn ngữ AFL tự định nghĩa:

  1. Syntax
  2. Các toán tử cơ bản (như cộng trừ nhân chia….) và ưu tiên làm việc trên các loại data nguyên thủy (scalar, float)
  3. Control Flow (Conditional execution hay Loop)
  4. Các khía niệm về cấu trúc (Biến, hàm, thủ tục, cấu trúc, đối tượng)
  5. 1 số công cụ khác như xử lý ngoại lệ….

Amibroker không phải là một mã nguồn mở (open source) như Python, R hay C++, vì vây các thư viện trong Amibroker chỉ có thể được cung cấp bới

  1. #include – import các library viết trước dưới dạng “.afl”
  2. Amibroker Development kit
  3. Jscript/VBscript
  4. External COM object (cái này không hiểu lắm) http://www.amibroker.com/guide/a_aflcom.html

Ngoài ra người dùng có thể share các indicators lên forum của amibroker cho người khác dùng lại. Tuy nhiên customized indicators cho profit ngon thì thường bị exploited rất nhanh nên đến lúc up lên dc forum hay database thì profit cũng giảm mất rồi.
Ở khía cạnh quan trọng nhất, có thể hiểu AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.
Mảng (array) đơn giản là một tập hợp (1 hàng) các số liệu, cũng có thể hiểu nó là các vector. Amibroker lưu trong cơ sở dữ liệu mỗi cổ phiếu 6 mảng bao gồm: Giá mở cửa (opening price), giá đóng cửa (closing price), giá cao (high), giá thấp (low), khối lượng (volume) và các hợp đồng đang ở dạng Mở (Open Interest, không áp dụng cho dữ liệu cổ phiếu). Ví dụ dưới minh họa cách AFL lưu dữ liệu trong mảng :

1
Bất kì 1 mảng nào khác đều được tính toán dựa trên các toán tử và công thức xây dựng sẵn trong thư viện AFL từ 6 mảng cơ bản trong cơ sở dữ liệu của mỗi cổ phiếu. Mỗi giá trị trong 1 mảng đều có 1 giá trị ngày tháng tương ứng với nó.

2. Xử lý mảng và tốc độ của AFL

Giả sử có câu lệnh:  MyVariable = (High + Low) / 2
Khi AFL đánh giá 1 câu lệnh trên, đầu tiên nó lấy 2 mảng High và Low từ cơ sở dữ liệu của cổ phiếu, sau đó thêm các mảng tương ứng. Nói 1 cách khác,bước đầu tiên,  toán tử “+” (và có thể áp dụng với các toán tử khác) được xử lý để tạo ra một mảng khác, sau đó mảng mới sẽ tiếp tục được áp dụng toán tử “/” ở bước tiếp theo và được gán cho biến MyVariable.

2.PNG

3. Trung bình trượt (Moving Averages), và câu lệnh điều kiện (Conditional Statements)

Có đoạn code:

Cond1 = Close > MA (Close, 5);
Cond2 = Volume > Ref (Volume, -1);
Buy = Cond1 and Cond2;
Sell = High > 1.3;

Đoạn code trên tạo ra tín hiệu mua khi giá đóng cửa ngày hôm nay lớn hơn đường MA(3) và khối lượng ngày áp dụng lớn hơn khối lượng ngày trước đó. Nó cũng tạo ra tín hiệu bán khi giá cao nhất trong ngày áp dụng lớn hơn 1.30.
Nếu trong đoạn code, người dùng cần xem xét giả thiết giá đóng cửa lớn hơn giá trị MA(3), AFL đầu tiên sẽ quét mảng close price và tạo ra  một mảng mới MA(close, 3) cho mỗi cổ phiếu cần phân tích. Mỗi ô giá trị (cell) trong mảng mới sẽ được so sánh với mảng close price. Từ kết quả so sánh, AFL lại tạo ra một mảng mới, có tên Cond1, nếu close price lớn hơn giá trị MA(close,3) tương ứng, ô giá trị tương ứng trong mảng Cond1 được gán giá trị 1. Nếu close price nhỏ hơn MA(close,3),  ô giá trị tương ứng trong mảng Cond1 được gán giá trị 0.
AFL có thể nhìn về cả 2 phía mỗi ô giá trị trong chuỗi thời gian, bằng cách sử dụng hàm Ref. Tương tự như trên, lần lượt mảng Ref(Volume, -1) và mảng Cond2 được tạo ra từ database gốc. Sau đó mảng Buy và mảng Sell lần lượt được thiết lập, chi tiết như sau:

3.PNG
Buy và Sell là 2 mảng đặc biệt, khi tạo biến với tên “Buy” và “Sell”, AFL ngầm hiểu đó là tín hiệu để mua và bán.

4. Hàm plot() trong Amibroker

plot(array, name, color, style = styleLine,
     minvalue = Null, maxvalue = Null,
     XShift = 0, ZOrder = 0, width = 1)

Các tham số bao gồm:

  • Tham số array đại diện cho số liệu sẽ được vẽ
  • Tham số name đại diện cho tên của đồ thị
  • Tham số color dùng để thể hiện màu sắc cho đồ thị
  • Tham số style định nghĩa dạng đồ thị được vẽ (line/histogram/candlestick/bar, etc….)
Plot(RSI(16), "Draft RSI", colorBlueGrey);

Câu lệnh trên khi apply cho chart như hình bên dưới:

4.PNG

  • Tham số Zorder được dùng để tạo các layer cho object cần được vẽ, value sẽ tương ứng như sau:
5
  • Tham số Zshift phức tạp hơn một chút. Lấy 1 ví dụ cụ thể, tạo 1 chart từ dữ liệu close price kết hợp với đường MA(15) như đoạn code bên dưới:
Plot(Close, "Adjusted Close", colorDefault, styleBar);
Plot(MA(Close, 15), "MA-15", colorRed, styledashed);
Plot(MA(Close, 15), "MA-Shift", colorBlue, styleThick, Null, Null, 10);

6
Ta được đồ thị như hình, cụ thể tham số Zshift có tác dụng shift object sang phải hoặc sang trái = số bar đã input, ở trường hợp này là 10.
Một điểm khá hay cần lưu ý đó là khả năng thay đổi màu sắc (Dynamic Color) khi các object (indicator, price,…) thỏa mãn một số điều kiện cho trước (sử dụng hàm Iff )

1
2
dynamic_color = IIf(MACD() > 0 , colorBlue, colorRed);
Plot(MACD(), "My MACD", dynamic_color, styleHistogram | styleThick)

7.PNG
Có thể kết hợp nhiều style vào trong một chart dựa vào kí hiệu “|”, chi tiết về các style cụ thể có thể tìm ở trên website của Amibroker.
Một điểm rất hay khi sử dụng chart trong Amibroker đó là có thể thay đổi giá trị của đối số cho 1 một tham số trong một hàm. Giả sử thay vì cố định giá trị MA(16) với màu sắc, title và tên như ở phía trên, có thể tạo nên một vector các đối sốbằng các câu lệnh: Param, ParamStr, Paramcolor, Paramstyle, sau đó pass vector vào hàm. Ưu điểm ở chỗ có thể tùy chỉnh giá trị tham số ngay trên đồ thị. Phía dưới là 1 ví dụ đơn giản nhất có thể làm được.

period = Param(“MA1”, 16, 3, 50, 1);

Plot(MA(Close,period), “Simple Moving Average (” + period + “)”,

colorBrown,     stylethick);

8.PNG
Phía dưới là 1 ví dụ khác lấy từ thư viện AFL knowledge base:

ticker = ParamStr( "Ticker", "VNINDEX" );
sp = Param( "MA Period", 12, 2, 100 );
PlotForeign( ticker, "Chart of "+ticker,
ParamColor( "Price Color", colorBlack ), styleCandle );
Plot( MA( Foreign( ticker, "C" ), sp ), "MA",
ParamColor( "MA Color", colorRed ) );

9.PNG

 

Sau khi hiểu được nguyên lý hoạt động của Amibroker dựa trên nguyên lý vectorization (tính toán dựa trên các mảng có sẵn như OHLC hay volume), ở bài viết này tôi sẽ phân tích về chức năng Scan và Exploration trong Amibroker.
Một trong những tính năng mạnh nhất của Amibroker là khả năng quét (screen) hàng trăm cổ phiếu trong thời gian thực (real-time) và theo dõi tín hiệu giao dịch tương ứng. Việc quét này được thực hiện bởi chức năng “Scan” và “Exploration”.
Điểm khác biệt quan trọng nhất giữa Scan và Exploration là việc Exploration cho phép người dùng tùy chỉnh các output được hiện ra trong cửa sổ Analysis, trong khi Scan chỉ thực việc công việc tìm kiếm các tín hiệu buy/sell/short/cover và hiển thị chúng bằng các cột được định dạng từ trước.
Giả sử tôi mô phỏng 1 trading strategy dựa vào tin hiệu của MACD và Signal Line như đoạn code bên dưới:
1
2
Buy = Cross(MACD(), Signal());
Sell = Cross(Signal(), MACD());
sau  khi paste code vào formula rồi send to automatic analysis, tôi click scan và được kết quả như hình bên dưới:
11.png
Về cơ bản, chức năng của Scan chỉ có vậy. Amibroker sẽ quét tìm trong database lần lượt từng ngày các mã cổ phiếu thỏa mãn trading rule (buy/sell) mà người dùng input vào, mỗi khi các tín hiệu thỏa mãn, Amibroker sẽ trả về 1 row các giá trị bao gồm Symbol, Trade, Date và Close như hình phía bên trên.
Tiếp theo là chức năng exploration. Chức năng này mạnh hơn scan rất nhiều bởi người dùng có thể tự định nghĩa các giá trị trả về cho column, không còn giới hạn bởi 4 cột như scan.
Giả sử tôi muốn quét toàn bộ database của HOSE và HNX, sau đó trả về tên mã cổ phiếu (Symbol), Ngày (Date), Giá đóng cửa (Close) và khối lượng (Volume), tôi sẽ viết đoạn code như sau:
Filter = 1;
AddColumn(DateTime(), "Date", formatDateTime);
AddColumn(Close, "Close", 1.2);
AddColumn(Volume, "Volume", 1);
Đầu tiên là dòng Filter = 1. Exploration trong Amibroker yêu cầu người dùng sử dụng hàm Filter khi sử dụng chức năng exploration, nếu trong code không có hàm Filter, exploration sẽ không chạy. Filter = 1 được dùng để quét toàn bộ các mã có trong cửa sổ apply to trong automatic analysis. Giả sử tôi để apply to: All quotes, amibroker sẽ quét toàn bộ các mã có trong database. Nếu chuyển thành apply to: watchlist0, amibroker sẽ quét toàn bộ các mã có trong watchlist0. Ngoài ra trong exploration, người dùng có thể chọn range tùy ý trên thanh công cụ, có thể là 1 recent-bar hoặc 1 khoảng nào đó (from … to …). Hàm AddColumn được dùng để tạo các cột trong output của Automatic Analysis, có thể check help index để xem chi tiết các parameters cần thiết để pass vào function. Lưu ý là khi dùng hàm exploration, mặc định sẽ có 2 cột là Ticker và Date/Time, người dùng không nhất thiết phải thêm vào.
2.png

 

Xong phần cơ bản, giờ tôi sẽ đào sâu thêm 1 chút về hàm Filter. Ở mặt code, giá trị để truyền vào hàm Filter sẽ có dạng boolean. Ở ví dụ trên, Filter = 1 tương đương Filter = True. Giờ giả sử tôi muốn lọc ra các cổ phiếu có khối lượng giao dịch lớn hơn 10000 trong phiên gần nhất, tôi sẽ đặt Filter = Volume > 10000, Hàm Filter sẽ trả về các ticker trong danh sách apply to thỏa mãn điều kiện trên, sau đó các hàm phía dưới Filter sẽ thực hiện dựa trên list trả về bởi hàm filter. Giả sử tôi có mã KDC, VGC và AAM trong watchlist. Hàm Filter phía trên sẽ lọc từ watchlist ra 2 mã thỏa mãn là KDC và VGC, sau đó các hàm Addcolumn phía bên dưới sẽ thực hiện nhiệm vụ dựa trên 2 mã này, mã AAM bị loại bỏ. Thử đoạn code bên dưới với apply to: All symbols và range : 1 recent-bar
Filter = Volume > 10000;
AddColumn(DateTime(), "Date", formatDateTime);
AddColumn(Close, "Close", 1.2);
AddColumn(Volume, "Volume", 1);
3

 

Amibroker cũng hỗ trợ thêm màu sắc vào trong các cột cũng như thêm các cột có giá trị String (không phải số hay ngày tháng). Đoạn code bên dưới sẽ minh họa ý tương này,
Filter = Volume > 10000;
CloseCond1 = Close > Ref(Close, -1);
CloseStatus1 = WriteIf(CloseCond1, "Close Bullish", "Close Bearish");
CloseColor1 = IIf(CloseCond1, colorGreen, colorRed);
AddColumn(Close, "Close", 1.2,colorWhite,CloseColor1);
AddTextColumn(CloseStatus1, "Close Status", 1, colorWhite, CloseColor1);
VolumeCond1 = Volume > Ref(Volume, -1);
VolumeStatus1 = WriteIf(VolumeCond1, "Volume Bullish", "Volume Bearish");
VolumeColor1 = IIf(VolumeCond1, colorGreen, colorRed);
AddColumn(Volume, "Volume", 1, colorWhite, VolumeColor1);
AddTextColumn(VolumeStatus1, "Close Status", 1, colorWhite, VolumeColor1);
Đoạn code này sẽ in ra các cột màu đỏ nếu Giá đóng cửa và khối lượng thấp hơn ngày hôm trước, màu xanh nếu ngược lại. Ngoài ra còn in thêm 2 cột có giá trị String tương ứng với Close và Volume.
4
Về mặt cơ bản, thế là hết cho scan và exploration. Giờ trader có thể tự thiết kế bộ lọc của riêng mình với các indicator cũng như các hàm khác nhau trong amibroker. Giả sử muốn lọc RSI > 70 và RSI < 30 chỉ cần type Filter = RSI > 70 or RSI < 30; hay muốn in ra các column thể hiện giá trị MA30 thì chỉ cần AddColumn(MA(30), “MA30”, 1.2); là xong. Ý tưởng thiết kế nội dung là của các bạn.

Leave a Reply

Your email address will not be published. Required fields are marked *