Tiền tệ cơ sở, tiền tệ định giá và tiền tệ ký quỹ của công cụ
Một trong những thuộc tính quan trọng nhất của mỗi công cụ tài chính là các loại tiền tệ hoạt động của nó:
- Tiền tệ cơ sở mà tài sản được mua hoặc bán được biểu thị (đối với các công cụ Forex)
- Tiền tệ tính toán lợi nhuận (tiền tệ định giá)
- Tiền tệ tính toán ký quỹ
Một chương trình MQL có thể lấy tên của các loại tiền tệ này bằng cách sử dụng hàm SymbolInfoString
và ba thuộc tính từ bảng sau.
Định danh | Mô tả |
---|---|
SYMBOL_CURRENCY_BASE | Tiền tệ cơ sở |
SYMBOL_CURRENCY_PROFIT | Tiền tệ lợi nhuận |
SYMBOL_CURRENCY_MARGIN | Tiền tệ ký quỹ |
Những thuộc tính này giúp phân tích các công cụ Forex, trong tên của chúng nhiều nhà môi giới thêm các tiền tố và hậu tố khác nhau, cũng như các công cụ giao dịch trên sàn. Đặc biệt, thuật toán sẽ có thể tìm một ký hiệu để lấy tỷ giá chéo của hai loại tiền tệ đã cho hoặc chọn một danh mục các chỉ số với một tiền tệ định giá chung đã cho.
Vì việc tìm kiếm công cụ theo các yêu cầu nhất định là một nhiệm vụ rất phổ biến, hãy tạo một lớp SymbolFilter
(SymbolFilter.mqh
) để xây dựng danh sách các ký hiệu phù hợp và các thuộc tính đã chọn của chúng. Trong tương lai, chúng ta sẽ sử dụng lớp này không chỉ để phân tích tiền tệ mà còn các đặc điểm khác.
Trước tiên, chúng ta sẽ xem xét một phiên bản đơn giản hóa và sau đó bổ sung thêm các chức năng tiện lợi.
Trong quá trình phát triển, chúng ta sẽ sử dụng các công cụ hỗ trợ có sẵn: một mảng bản đồ liên kết (MapArray.mqh) để lưu trữ các cặp khóa-giá trị của các loại đã chọn và một bộ giám sát thuộc tính ký hiệu (SymbolMonitor.mqh).
#include <MQL5Book/MapArray.mqh>
#include <MQL5Book/SymbolMonitor.mqh>
2
Để đơn giản hóa các câu lệnh tích lũy kết quả công việc trong mảng, chúng ta sử dụng phiên bản cải tiến của macro PUSH
, mà chúng ta đã thấy trong các ví dụ trước, cũng như phiên bản EXPAND
của nó cho các mảng đa chiều (việc gán đơn giản là không thể trong trường hợp này).
#define PUSH(A, V) (A[ArrayResize(A, ArraySize(A) + 1, ArraySize(A) * 2) - 1] = V)
#define EXPAND(A) (ArrayResize(A, ArrayRange(A, 0) + 1, ArrayRange(A, 0) * 2) - 1)
2
Một đối tượng của lớp SymbolFilter
phải có bộ nhớ cho các giá trị thuộc tính sẽ được sử dụng để lọc các ký hiệu. Do đó, chúng ta sẽ mô tả ba mảng MapArray
trong lớp cho các thuộc tính kiểu số nguyên, số thực và chuỗi.
class SymbolFilter
{
MapArray<ENUM_SYMBOL_INFO_INTEGER, long> longs;
MapArray<ENUM_SYMBOL_INFO_DOUBLE, double> doubles;
MapArray<ENUM_SYMBOL_INFO_STRING, string> strings;
...
2
3
4
5
6
Việc thiết lập các thuộc tính lọc cần thiết được thực hiện bằng cách sử dụng các phương thức let
được nạp chồng.
public:
SymbolFilter *let(const ENUM_SYMBOL_INFO_INTEGER property, const long value)
{
longs.put(property, value);
return &this;
}
SymbolFilter *let(const ENUM_SYMBOL_INFO_DOUBLE property, const double value)
{
doubles.put(property, value);
return &this;
}
SymbolFilter *let(const ENUM_SYMBOL_INFO_STRING property, const string value)
{
strings.put(property, value);
return &this;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Lưu ý rằng các phương thức trả về một con trỏ tới bộ lọc, điều này cho phép bạn viết các điều kiện dưới dạng chuỗi: ví dụ, nếu trước đó trong mã đã mô tả một đối tượng f
thuộc kiểu SymbolFilter
, thì bạn có thể áp đặt hai điều kiện về loại giá và tên của tiền tệ lợi nhuận như sau:
f.let(SYMBOL_CHART_MODE, SYMBOL_CHART_MODE_LAST).let(SYMBOL_CURRENCY_PROFIT, "USD");
Việc tạo mảng các ký hiệu thỏa mãn điều kiện được thực hiện bởi đối tượng bộ lọc trong một số biến thể của phương thức select
, trong đó phiên bản đơn giản nhất được trình bày dưới đây (các tùy chọn khác sẽ được thảo luận sau).
Tham số watch
xác định ngữ cảnh tìm kiếm các ký hiệu: trong số những ký hiệu được chọn trong Market Watch
(true
) hoặc tất cả các ký hiệu có sẵn (false
). Mảng đầu ra symbols
sẽ được điền với tên của các ký hiệu phù hợp. Chúng ta đã biết cấu trúc mã bên trong phương thức: nó có một vòng lặp qua các ký hiệu mà cho mỗi ký hiệu sẽ tạo ra một đối tượng giám sát m
.
void select(const bool watch, string &symbols[]) const
{
const int n = SymbolsTotal(watch);
for(int i = 0; i < n; ++i)
{
const string s = SymbolName(i, watch);
SymbolMonitor m(s);
if(match<ENUM_SYMBOL_INFO_INTEGER, long>(m, longs)
&& match<ENUM_SYMBOL_INFO_DOUBLE, double>(m, doubles)
&& match<ENUM_SYMBOL_INFO_STRING, string>(m, strings))
{
PUSH(symbols, s);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Chính nhờ bộ giám sát mà chúng ta có thể lấy giá trị của bất kỳ thuộc tính nào theo cách thống nhất. Việc kiểm tra xem các thuộc tính của ký hiệu hiện tại có khớp với tập hợp các điều kiện đã lưu trong các mảng longs
, doubles
và strings
hay không được thực hiện bởi một phương thức trợ giúp match
. Chỉ khi tất cả các thuộc tính được yêu cầu khớp, tên ký hiệu mới được lưu vào mảng đầu ra symbols
.
Trong trường hợp đơn giản nhất, việc thực hiện phương thức match
như sau (sau này nó sẽ được thay đổi).
protected:
template<typename K, typename V>
bool match(const SymbolMonitor &m, const MapArray<K, V> &data) const
{
for(int i = 0; i < data.getSize(); ++i)
{
const K key = data.getKey(i);
if(!equal(m.get(key), data.getValue(i)))
{
return false;
}
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Nếu ít nhất một trong các giá trị trong mảng data
không khớp với thuộc tính đặc trưng tương ứng, phương thức trả về false
. Nếu tất cả các thuộc tính khớp (hoặc không có điều kiện cho các thuộc tính thuộc loại này), phương thức trả về true
.
Việc so sánh hai giá trị được thực hiện bằng cách sử dụng equal
. Do thực tế rằng trong số các thuộc tính có thể có các thuộc tính kiểu double
, việc thực hiện không đơn giản như người ta có thể nghĩ.
template<typename V>
static bool equal(const V v1, const V v2)
{
return v1 == v2 || eps(v1, v2);
}
2
3
4
5
Đối với kiểu double
, biểu thức v1 == v2
có thể không hoạt động với các số gần nhau, và do đó cần tính đến độ chính xác của kiểu số thực DBL_EPSILON
. Điều này được thực hiện trong một phương thức riêng eps
, được nạp chồng riêng cho kiểu double
và tất cả các kiểu khác do mẫu.
static bool eps(const double v1, const double v2)
{
return fabs(v1 - v2) < DBL_EPSILON * fmax(v1, v2);
}
template<typename V>
static bool eps(const V v1, const V v2)
{
return false;
}
2
3
4
5
6
7
8
9
10
Khi các giá trị của bất kỳ kiểu nào trừ double
bằng nhau, phương thức mẫu eps
sẽ không được gọi, và trong tất cả các trường hợp khác (bao gồm khi các giá trị khác nhau), nó trả về false
như yêu cầu (do đó, chỉ có điều kiện v1 == v2
).
Tùy chọn bộ lọc được mô tả ở trên chỉ cho phép kiểm tra các thuộc tính theo đẳng thức. Tuy nhiên, trong thực tế, thường cần phân tích các điều kiện cho bất đẳng thức, cũng như lớn hơn/nhỏ hơn. Vì lý do này, lớp SymbolFilter
có liệt kê IS
với các phép toán so sánh cơ bản (nếu muốn, nó có thể được bổ sung).
class SymbolFilter
{
...
enum IS
{
EQUAL,
GREATER,
NOT_EQUAL,
LESS
};
...
2
3
4
5
6
7
8
9
10
11
Đối với mỗi thuộc tính từ các liệt kê ENUM_SYMBOL_INFO_INTEGER
, ENUM_SYMBOL_INFO_DOUBLE
và ENUM_SYMBOL_INFO_STRING
, cần lưu không chỉ giá trị thuộc tính mong muốn (nhắc lại về các mảng liên kết longs
, doubles
, strings
), mà còn phương thức so sánh từ liệt kê IS
mới.
Vì các phần tử của các liệt kê tiêu chuẩn có giá trị không chồng lấp (có một ngoại lệ liên quan đến khối lượng nhưng không quan trọng), việc dành sẵn một mảng bản đồ chung conditions
cho phương thức so sánh là hợp lý. Điều này đặt ra câu hỏi nên chọn kiểu nào cho khóa bản đồ để "kết hợp" các liệt kê khác nhau về mặt kỹ thuật. Để làm điều này, chúng ta phải mô tả liệt kê giả ENUM_ANY
chỉ biểu thị một loại liệt kê chung nhất định. Hãy nhớ rằng tất cả các liệt kê có biểu diễn nội bộ tương đương với số nguyên int
, và do đó có thể được quy về nhau.
enum ENUM_ANY
{
};
MapArray<ENUM_ANY, IS> conditions;
MapArray<ENUM_ANY, long> longs;
MapArray<ENUM_ANY, double> doubles;
MapArray<ENUM_ANY, string> strings;
...
2
3
4
5
6
7
8
9
Bây giờ chúng ta có thể hoàn thiện tất cả các phương thức let
đặt giá trị mong muốn của thuộc tính bằng cách thêm tham số đầu vào cmp
chỉ định phương thức so sánh. Theo mặc định, nó đặt kiểm tra đẳng thức (EQUAL
).
SymbolFilter *let(const ENUM_SYMBOL_INFO_INTEGER property, const long value,
const IS cmp = EQUAL)
{
longs.put((ENUM_ANY)property, value);
conditions.put((ENUM_ANY)property, cmp);
return &this;
}
2
3
4
5
6
7
Đây là biến thể cho các thuộc tính số nguyên. Hai overload khác thay đổi theo cách tương tự.
Tính đến thông tin mới về các cách so sánh khác nhau và đồng thời loại bỏ các kiểu khóa khác nhau trong mảng bản đồ, chúng ta sửa đổi phương thức match
. Trong đó, đối với mỗi thuộc tính được chỉ định, chúng ta lấy một điều kiện từ mảng conditions
dựa trên khóa trong mảng bản đồ data
, và các kiểm tra thích hợp được thực hiện bằng toán tử switch
.
template<typename V>
bool match(const SymbolMonitor &m, const MapArray<ENUM_ANY, V> &data) const
{
// biến giả để chọn overload phương thức m.get bên dưới
static const V type = (V)NULL;
// vòng lặp qua các điều kiện áp đặt lên thuộc tính của ký hiệu
for(int i = 0; i < data.getSize(); ++i)
{
const ENUM_ANY key = data.getKey(i);
// lựa chọn phương thức so sánh trong điều kiện
switch(conditions[key])
{
case EQUAL:
if(!equal(m.get(key, type), data.getValue(i))) return false;
break;
case NOT_EQUAL:
if(equal(m.get(key, type), data.getValue(i))) return false;
break;
case GREATER:
if(!greater(m.get(key, type), data.getValue(i))) return false;
break;
case LESS:
if(greater(m.get(key, type), data.getValue(i))) return false;
break;
}
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Phương thức mẫu mới greater
được thực hiện một cách đơn giản.
template<typename V>
static bool greater(const V v1, const V v2)
{
return v1 > v2;
}
2
3
4
5
Bây giờ lời gọi phương thức match
có thể được viết dưới dạng ngắn gọn hơn vì kiểu mẫu duy nhất còn lại V
được tự động xác định bởi tham số data
được truyền vào (và đây là một trong các mảng longs
, doubles
, hoặc strings
).
void select(const bool watch, string &symbols[]) const
{
const int n = SymbolsTotal(watch);
for(int i = 0; i < n; ++i)
{
const string s = SymbolName(i, watch);
SymbolMonitor m(s);
if(match(m, longs)
&& match(m, doubles)
&& match(m, strings))
{
PUSH(symbols, s);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Đây chưa phải là phiên bản cuối cùng của lớp SymbolFilter
, nhưng chúng ta đã có thể thử nghiệm nó trong thực tế.
Hãy tạo một kịch bản SymbolFilterCurrency.mq5
có thể lọc các ký hiệu dựa trên các thuộc tính của tiền tệ cơ sở và tiền tệ lợi nhuận; trong trường hợp này là USD. Tham số MarketWatchOnly
mặc định chỉ tìm kiếm trong Market Watch
.
#include <MQL5Book/SymbolFilter.mqh>
input bool MarketWatchOnly = true;
void OnStart()
{
SymbolFilter f; // đối tượng bộ lọc
string symbols[]; // mảng cho kết quả
...
2
3
4
5
6
7
8
9
Giả sử rằng chúng ta muốn tìm các công cụ Forex có báo giá trực tiếp, tức là "USD" xuất hiện trong tên của chúng ở đầu. Để không phụ thuộc vào cách hình thành tên cụ thể của một nhà môi giới, chúng ta sẽ sử dụng thuộc tính SYMBOL_CURRENCY_BASE
, chứa tiền tệ đầu tiên.
Hãy ghi lại điều kiện rằng tiền tệ cơ sở của ký hiệu bằng USD và áp dụng bộ lọc.
f.let(SYMBOL_CURRENCY_BASE, "USD")
.select(MarketWatchOnly, symbols);
Print("===== Base is USD =====");
ArrayPrint(symbols);
...
2
3
4
5
Mảng kết quả được xuất ra nhật ký.
===== Base is USD =====
"USDCHF" "USDJPY" "USDCNH" "USDRUB" "USDCAD" "USDSEK" "SP500m" "Brent"
2
Như bạn thấy, mảng bao gồm không chỉ các ký hiệu Forex với USD ở đầu ticker mà còn chỉ số S&P500 và hàng hóa (dầu). Hai ký hiệu cuối cùng được định giá bằng đô la, nhưng chúng cũng có cùng tiền tệ cơ sở. Đồng thời, tiền tệ định giá của các ký hiệu Forex (cũng là tiền tệ lợi nhuận) đứng thứ hai và khác với USD. Điều này cho phép bạn bổ sung bộ lọc để các ký hiệu không phải Forex không còn khớp với nó nữa.
Hãy xóa mảng, thêm điều kiện rằng tiền tệ lợi nhuận không bằng "USD", và yêu cầu lại các ký hiệu phù hợp (điều kiện trước đó đã được lưu trong đối tượng f
).
...
ArrayResize(symbols, 0);
f.let(SYMBOL_CURRENCY_PROFIT, "USD", SymbolFilter::IS::NOT_EQUAL)
.select(MarketWatchOnly, symbols);
Print("===== Base is USD and Profit is not USD =====");
ArrayPrint(symbols);
}
2
3
4
5
6
7
8
Lần này, chỉ những ký hiệu bạn đang tìm kiếm thực sự được hiển thị trong nhật ký.
===== Base is USD and Profit is not USD =====
"USDCHF" "USDJPY" "USDCNH" "USDRUB" "USDCAD" "USDSEK"
2