Đặc điểm giao dịch của biểu tượng tùy chỉnh
Biểu tượng tùy chỉnh chỉ được biết đến bởi terminal khách hàng và không khả dụng trên máy chủ giao dịch. Do đó, nếu một biểu tượng tùy chỉnh được xây dựng dựa trên một biểu tượng thực tế nào đó, thì bất kỳ Expert Advisor nào được đặt trên biểu đồ của biểu tượng tùy chỉnh đó nên tạo ra các lệnh giao dịch cho biểu tượng gốc.
Là giải pháp đơn giản nhất cho vấn đề này, bạn có thể đặt một Expert Advisor trên biểu đồ của biểu tượng gốc nhưng nhận tín hiệu (ví dụ, từ các chỉ báo) từ biểu tượng tùy chỉnh. Một cách tiếp cận rõ ràng khác là thay thế tên của các biểu tượng khi thực hiện các hoạt động giao dịch. Để kiểm tra cả hai cách tiếp cận, chúng ta cần một biểu tượng tùy chỉnh và một Expert Advisor.
Ví dụ thực tế thú vị về biểu tượng tùy chỉnh, hãy xem xét một số biểu đồ equivolume khác nhau.
Biểu đồ equivolume (khối lượng bằng nhau) là biểu đồ các thanh được xây dựng dựa trên nguyên tắc bình đẳng của khối lượng chứa trong chúng. Trên biểu đồ thông thường, mỗi thanh mới được hình thành theo tần suất được chỉ định, trùng với kích thước khung thời gian. Trên biểu đồ equivolume, mỗi thanh được coi là hình thành khi tổng số tick hoặc khối lượng thực đạt đến giá trị đặt trước. Tại thời điểm này, chương trình bắt đầu tính toán số lượng cho thanh tiếp theo. Tất nhiên, trong quá trình tính toán khối lượng, các chuyển động giá được kiểm soát, và chúng ta nhận được các tập hợp giá thông thường trên biểu đồ: Open
, High
, Low
, và Close
.
Các thanh phạm vi bằng nhau được xây dựng theo cách tương tự: một thanh mới mở ra khi giá vượt qua một số điểm nhất định theo bất kỳ hướng nào.
Do đó, Expert Advisor EqualVolumeBars.mq5
sẽ hỗ trợ ba chế độ, tức là ba loại biểu đồ:
- EqualTickVolumes — các thanh equivolume theo tick
- EqualRealVolumes — các thanh equivolume theo khối lượng thực (nếu chúng được phát sóng)
- RangeBars — các thanh phạm vi bằng nhau
Chúng được chọn bằng tham số đầu vào WorkMode
.
Kích thước thanh và độ sâu lịch sử để tính toán được chỉ định trong các tham số TicksInBar
và StartDate
.
input int TicksInBar = 1000;
input datetime StartDate = 0;
2
Tùy thuộc vào chế độ, biểu tượng tùy chỉnh sẽ nhận được hậu tố _Eqv
, _Qrv
hoặc _Rng
, tương ứng, với việc bổ sung kích thước thanh.
Mặc dù trục ngang trên biểu đồ Equivolume/Equal-Range vẫn thể hiện thứ tự thời gian, nhưng các dấu thời gian của mỗi thanh là tùy ý và phụ thuộc vào biến động (số lượng hoặc kích thước giao dịch) trong mỗi khung thời gian. Về mặt này, khung thời gian của biểu đồ biểu tượng tùy chỉnh nên được chọn bằng với tối thiểu M1.
Hạn chế của nền tảng là tất cả các thanh có cùng thời lượng danh nghĩa, nhưng trong trường hợp các biểu đồ "nhân tạo" của chúng ta, cần nhớ rằng thời lượng thực tế của mỗi thanh là khác nhau và có thể vượt quá 1 phút đáng kể hoặc ngược lại, ít hơn. Vì vậy, với một khối lượng đủ nhỏ được chỉ định cho một thanh, có thể xảy ra tình huống mà các thanh mới được hình thành thường xuyên hơn một lần mỗi phút, và sau đó thời gian ảo của các thanh biểu tượng tùy chỉnh sẽ chạy trước thời gian thực, vào tương lai. Để ngăn điều này xảy ra, bạn nên tăng khối lượng của thanh (tham số
TicksInBar
) hoặc di chuyển các thanh cũ sang trái.
Việc khởi tạo và các tác vụ phụ trợ khác để quản lý biểu tượng tùy chỉnh (đặc biệt là đặt lại lịch sử hiện có và mở biểu đồ với biểu tượng mới) được thực hiện tương tự như trong các ví dụ khác, và chúng ta sẽ bỏ qua chúng. Hãy chuyển sang các chi tiết cụ thể mang tính ứng dụng.
Chúng ta sẽ đọc lịch sử tick thực bằng các hàm tích hợp sẵn CopyTicks
/CopyTicksRange
: hàm đầu tiên để lấy lịch sử theo lô 10,000 tick, và hàm thứ hai để yêu cầu các tick mới kể từ lần xử lý trước đó. Tất cả chức năng này được đóng gói trong lớp TicksBuffer
(mã nguồn đầy đủ được đính kèm).
class TicksBuffer
{
private:
MqlTick array[]; // mảng nội bộ của các tick
int tick; // chỉ số tăng dần của tick tiếp theo để đọc
public:
bool fill(ulong &cursor, const bool history = false);
bool read(MqlTick &t);
};
2
3
4
5
6
7
8
9
Phương thức công khai fill
được thiết kế để điền vào mảng nội bộ với phần tick tiếp theo, bắt đầu từ thời gian cursor
(tính bằng mili giây). Đồng thời, thời gian trong cursor
ở mỗi lần gọi sẽ di chuyển về phía trước dựa trên thời gian của tick cuối cùng được đọc vào bộ đệm (lưu ý rằng tham số được truyền bằng tham chiếu).
Tham số history
xác định liệu sử dụng CopyTicks
hay CopyTicksRange
. Thông thường, trực tuyến, chúng ta sẽ đọc một hoặc nhiều tick mới từ trình xử lý OnTick
.
Phương thức read
trả về một tick từ mảng nội bộ và dịch chuyển con trỏ nội bộ (tick
) đến tick tiếp theo. Nếu cuối mảng được đạt tới trong khi đọc, phương thức sẽ trả về false
, có nghĩa là đã đến lúc gọi phương thức fill
.
Sử dụng các phương thức này, thuật toán duyệt lịch sử tick được triển khai như sau (mã này được gọi gián tiếp từ OnInit
qua bộ đếm thời gian).
ulong cursor = StartDate * 1000;
TicksBuffer tb;
while(tb.fill(cursor, true) && !IsStopped())
{
MqlTick t;
while(tb.read(t))
{
HandleTick(t, true);
}
}
2
3
4
5
6
7
8
9
10
11
Trong hàm HandleTick
, cần tính đến các thuộc tính của tick t
trong một số biến toàn cục kiểm soát số lượng tick, tổng khối lượng giao dịch (thực, nếu có), cũng như khoảng cách chuyển động giá. Tùy thuộc vào chế độ hoạt động, các biến này nên được phân tích khác nhau để xác định điều kiện hình thành thanh mới. Vì vậy, nếu trong chế độ equivolume, số lượng tick vượt quá TicksInBar
, chúng ta nên bắt đầu một thanh mới bằng cách đặt lại bộ đếm về 1. Trong trường hợp này, thời gian của thanh mới được lấy là thời gian tick được làm tròn đến phút gần nhất.
Nhóm biến toàn cục này cung cấp để lưu trữ thời gian ảo của thanh cuối cùng ("hiện tại") trên biểu tượng tùy chỉnh (now_time
), giá OHLC của nó, và khối lượng.
datetime now_time;
double now_close, now_open, now_low, now_high;
long now_volume, now_real;
2
3
Các biến được cập nhật liên tục cả trong quá trình đọc lịch sử và sau đó khi Expert Advisor bắt đầu xử lý các tick trực tuyến theo thời gian thực (chúng ta sẽ quay lại điều này sau).
Ở dạng đơn giản hóa một chút, thuật toán bên trong HandleTick
trông như thế này:
void HandleTick(const MqlTick &t, const bool history = false)
{
now_volume++; // đếm số lượng tick
now_real += (long)t.volume; // cộng dồn tất cả khối lượng thực
if(!IsNewBar()) // tiếp tục thanh hiện tại
{
if(t.bid < now_low) now_low = t.bid; // theo dõi biến động giá đi xuống
if(t.bid > now_high) now_high = t.bid; // và đi lên
now_close = t.bid; // cập nhật giá đóng cửa
if(!history)
{
// cập nhật thanh hiện tại nếu chúng ta không trong lịch sử
WriteToChart(now_time, now_open, now_low, now_high, now_close,
now_volume - !history, now_real);
}
}
else // thanh mới
{
do
{
// lưu thanh đã đóng với tất cả các thuộc tính
WriteToChart(now_time, now_open, now_low, now_high, now_close,
WorkMode == EqualTickVolumes ? TicksInBar : now_volume,
WorkMode == EqualRealVolumes ? TicksInBar : now_real);
// làm tròn thời gian lên phút cho thanh mới
datetime time = t.time / 60 * 60;
// ngăn các thanh có thời gian cũ hoặc giống nhau
// nếu đã đi vào "tương lai", chỉ cần lấy phút M1 tiếp theo
if(time <= now_time) time = now_time + 60;
// bắt đầu một thanh mới từ giá hiện tại
now_time = time;
now_open = t.bid;
now_low = t.bid;
now_high = t.bid;
now_close = t.bid;
now_volume = 1; // tick đầu tiên trong thanh mới
if(WorkMode == EqualRealVolumes) now_real -= TicksInBar;
now_real += (long)t.volume; // khối lượng thực ban đầu trong thanh mới
// lưu thanh mới 0
WriteToChart(now_time, now_open, now_low, now_high, now_close,
now_volume - !history, now_real);
}
while(IsNewBar() && WorkMode == EqualRealVolumes);
}
}
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Tham số history
xác định liệu tính toán dựa trên lịch sử hay đã ở thời gian thực (trên các tick trực tuyến đến). Nếu dựa trên lịch sử, chỉ cần hình thành mỗi thanh một lần, trong khi trực tuyến, thanh hiện tại được cập nhật với mỗi tick. Điều này cho phép tăng tốc xử lý lịch sử.
Hàm trợ giúp IsNewBar
trả về true
khi điều kiện đóng thanh tiếp theo theo chế độ được đáp ứng.
bool IsNewBar()
{
if(WorkMode == EqualTickVolumes)
{
if(now_volume > TicksInBar) return true;
}
else if(WorkMode == EqualRealVolumes)
{
if(now_real > TicksInBar) return true;
}
else if(WorkMode == RangeBars)
{
if((now_high - now_low) / _Point > TicksInBar) return true;
}
return false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hàm WriteToChart
tạo ra một thanh với các đặc điểm đã cho bằng cách gọi CustomRatesUpdate
.
void WriteToChart(datetime t, double o, double l, double h, double c, long v, long m = 0)
{
MqlRates r[1];
r[0].time = t;
r[0].open = o;
r[0].low = l;
r[0].high = h;
r[0].close = c;
r[0].tick_volume = v;
r[0].spread = 0;
r[0].real_volume = m;
if(CustomRatesUpdate(SymbolName, r) < 1)
{
Print("CustomRatesUpdate failed: ", _LastError);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Vòng lặp đọc và xử lý tick được đề cập ở trên được thực hiện trong lần truy cập ban đầu vào lịch sử, sau khi tạo hoặc tính toán lại hoàn toàn một biểu tượng người dùng đã tồn tại. Khi nói đến các tick mới, hàm OnTick
sử dụng mã tương tự nhưng không có cờ "lịch sử".
void OnTick()
{
static ulong cursor = 0;
MqlTick t;
if(cursor == 0)
{
if(SymbolInfoTick(_Symbol, t))
{
HandleTick(t);
cursor = t.time_msc + 1;
}
}
else
{
TicksBuffer tb;
while(tb.fill(cursor))
{
while(tb.read(t))
{
HandleTick(t);
}
}
}
RefreshWindow(now_time);
}
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
Hàm RefreshWindow
thêm một tick biểu tượng tùy chỉnh vào Market Watch
.
Lưu ý rằng việc chuyển tiếp tick làm tăng bộ đếm tick trong thanh lên 1, và do đó, khi ghi bộ đếm tick vào thanh thứ 0, chúng ta đã trừ đi một trước đó (xem biểu thức now_volume - !history
khi gọi WriteToChart
).
Việc tạo tick rất quan trọng vì nó kích hoạt sự kiện OnTick
trên các biểu đồ công cụ tùy chỉnh, điều này có thể cho phép các Expert Advisor đặt trên các biểu đồ đó giao dịch. Tuy nhiên, công nghệ này đòi hỏi một số thủ thuật bổ sung, mà chúng ta sẽ xem xét sau.
void RefreshWindow(const datetime t)
{
MqlTick ta[1];
SymbolInfoTick(_Symbol, ta[0]);
ta[0].time = t;
ta[0].time_msc = t * 1000;
if(CustomTicksAdd(SymbolName, ta) == -1)
{
Print("CustomTicksAdd failed:", _LastError, " ", (long) ta[0].time);
ArrayPrint(ta);
}
}
2
3
4
5
6
7
8
9
10
11
12
Chúng ta nhấn mạnh rằng thời gian của tick tùy chỉnh được tạo ra luôn được đặt bằng với nhãn của thanh hiện tại vì chúng ta không thể để lại thời gian tick thực: nếu nó đã vượt quá 1 phút và chúng ta gửi tick đó đến Market Watch
, terminal sẽ tạo thanh M1 tiếp theo, điều này sẽ phá vỡ cấu trúc "equivolume" của chúng ta vì các thanh của chúng ta không được hình thành theo thời gian, mà theo mức độ lấp đầy khối lượng (và chính chúng ta kiểm soát quá trình này).
Về lý thuyết, chúng ta có thể thêm một mili giây vào mỗi tick, nhưng chúng ta không có gì đảm bảo rằng thanh sẽ không cần lưu trữ hơn 60,000 tick (ví dụ, nếu người dùng yêu cầu một biểu đồ với một phạm vi giá nhất định không thể dự đoán được về số lượng tick cần thiết cho chuyển động như vậy).
Trong các chế độ theo khối lượng, về lý thuyết có thể nội suy thành phần giây và mili giây của thời gian tick bằng các công thức tuyến tính:
- EqualTickVolumes —
(now_volume - 1) * 60000 / TicksInBar
; - EqualRealVolumes —
(now_real - 1) * 60000 / TicksInBar
;
Tuy nhiên, đây không gì khác ngoài một phương tiện để nhận diện các tick, chứ không phải nỗ lực làm cho thời gian của các tick "nhân tạo" gần với thời gian của các tick thực. Điều này không chỉ liên quan đến việc mất đi sự không đều đặn của dòng tick thực, mà bản thân nó đã dẫn đến sự khác biệt về giá giữa biểu tượng gốc và biểu tượng tùy chỉnh được tạo ra dựa trên nó.
Vấn đề chính là cần phải làm tròn thời gian tick dọc theo ranh giới của thanh M1 và "đóng gói" chúng trong vòng một phút (xem phần bên lề về các loại biểu đồ đặc biệt). Ví dụ, tick tiếp theo với thời gian thực là 12:37:05'123
trở thành tick thứ 1001 và nên hình thành một thanh equivolume mới. Tuy nhiên, thanh M1 chỉ có thể được đánh dấu thời gian đến phút, tức là 12:37. Kết quả là, giá thực của công cụ tại 12:37 sẽ không khớp với giá trong tick cung cấp giá Open
cho thanh equivolume 12:37. Ngoài ra, nếu 1000 tick tiếp theo kéo dài qua vài phút, chúng ta vẫn sẽ buộc phải "nén" thời gian của chúng để không chạm đến mốc 12:38.
Vấn đề mang tính chất hệ thống do lượng tử hóa thời gian khi các biểu đồ đặc biệt được mô phỏng bằng biểu đồ khung thời gian M1 tiêu chuẩn. Vấn đề này không thể được giải quyết hoàn toàn trên các biểu đồ như vậy. Nhưng khi tạo các biểu tượng tùy chỉnh với tick trong thời gian liên tục (ví dụ, với báo giá tổng hợp hoặc dựa trên dữ liệu phát trực tuyến từ các dịch vụ bên ngoài), vấn đề này không phát sinh.
Cần lưu ý rằng việc chuyển tiếp tick chỉ được thực hiện trực tuyến trong phiên bản này của bộ tạo, trong khi các tick tùy chỉnh không được tạo trên lịch sử! Điều này được thực hiện để tăng tốc quá trình tạo báo giá. Nếu bạn cần tạo lịch sử tick bất chấp quá trình chậm hơn, Expert Advisor
EqualVolumeBars.mq5
nên được điều chỉnh: loại bỏ hàmWriteToChart
và thực hiện toàn bộ việc tạo bằngCustomTicksReplace
/CustomTicksAdd
. Đồng thời, cần nhớ rằng thời gian gốc của các tick nên được thay thế bằng một thời gian khác, trong vòng một thanh phút, để không làm xáo trộn cấu trúc của biểu đồ equivolume đã hình thành.
Hãy xem cách hoạt động của EqualVolumeBars.mq5
. Dưới đây là biểu đồ hoạt động của EURUSD M15 với Expert Advisor đang chạy trong đó. Nó có biểu đồ equivolume, trong đó 1000 tick được phân bổ cho mỗi thanh.
Biểu đồ equivolume EURUSD với 1000 tick mỗi thanh được tạo bởi Expert Advisor EqualVolumeBars
Lưu ý rằng khối lượng tick trên tất cả các thanh là bằng nhau, ngoại trừ thanh cuối cùng, đang vẫn còn hình thành (việc đếm tick vẫn tiếp tục).
Thống kê được hiển thị trong nhật ký.
Creating "EURUSD.c_Eqv1000"
Processing tick history...
End of CopyTicks at 2022.06.15 12:47:51
Bar 0: 2022.06.15 12:40:00 866 0
2119 bars written in 10 sec
Open "EURUSD.c_Eqv1000" chart to view results
2
3
4
5
6
Hãy kiểm tra một chế độ hoạt động khác: phạm vi bằng nhau. Dưới đây là biểu đồ mà phạm vi của mỗi thanh là 250 điểm.
Biểu đồ phạm vi bằng nhau EURUSD với các thanh 250 điểm được tạo bởi EqualVolumeBars
Đối với các công cụ giao dịch, Expert Advisor cho phép sử dụng chế độ khối lượng thực, ví dụ như sau:
Biểu đồ gốc và equivolume của Ethereum với khối lượng thực 10000 mỗi thanh
Khung thời gian của biểu tượng hoạt động khi đặt Expert Advisor tạo không quan trọng, vì lịch sử tick luôn được sử dụng cho các tính toán.
Đồng thời, khung thời gian của biểu đồ biểu tượng tùy chỉnh phải bằng M1 (nhỏ nhất có sẵn trong terminal). Do đó, thời gian của các thanh, theo quy tắc, tương ứng gần nhất có thể (trong phạm vi có thể) với thời điểm hình thành của chúng. Tuy nhiên, trong các chuyển động mạnh trên thị trường, khi số lượng tick hoặc kích thước khối lượng hình thành nhiều thanh mỗi phút, thời gian của các thanh sẽ chạy trước thời gian thực. Khi thị trường bình ổn, tình hình với các dấu thời gian của các thanh equivolume sẽ trở lại bình thường. Điều này không ảnh hưởng đến dòng giá trực tuyến, nên có lẽ không đặc biệt quan trọng, vì toàn bộ ý nghĩa của việc sử dụng các thanh equivolume hoặc phạm vi bằng nhau là để tách khỏi thời gian tuyệt đối.
Thật không may, tên của biểu tượng gốc và biểu tượng tùy chỉnh được tạo dựa trên nó không thể được liên kết bằng bất kỳ cách nào thông qua chính nền tảng. Sẽ rất tiện lợi nếu có một trường chuỗi origin
(nguồn) trong số các thuộc tính của biểu tượng tùy chỉnh, trong đó chúng ta có thể ghi tên của công cụ làm việc thực tế. Theo mặc định, nó sẽ trống, nhưng nếu được điền, nền tảng có thể thay thế biểu tượng trong tất cả các lệnh giao dịch và yêu cầu lịch sử, và thực hiện điều đó một cách tự động và minh bạch cho người dùng. Về lý thuyết, trong số các thuộc tính của các biểu tượng do người dùng định nghĩa, có một trường SYMBOL_BASIS
phù hợp về ý nghĩa, nhưng vì chúng ta không thể đảm bảo rằng các bộ tạo biểu tượng tùy chỉnh bất kỳ (bất kỳ chương trình MQL nào) sẽ điền đúng hoặc sử dụng nó chính xác cho mục đích này, chúng ta không thể dựa vào việc sử dụng nó.
Vì cơ chế này không có trong nền tảng, chúng ta sẽ cần tự mình triển khai nó. Bạn sẽ phải thiết lập sự tương ứng giữa các tên của biểu tượng nguồn và biểu tượng người dùng bằng các tham số.
Để giải quyết vấn đề, chúng ta đã phát triển lớp CustomOrder
(xem tệp đính kèm CustomOrder.mqh
). Nó chứa các phương thức bao bọc cho tất cả các hàm API MQL liên quan đến việc gửi lệnh giao dịch và yêu cầu lịch sử, có tham số chuỗi với tên biểu tượng. Trong các phương thức này, biểu tượng tùy chỉnh được thay thế bằng biểu tượng làm việc hiện tại hoặc ngược lại. Các hàm API khác không yêu cầu "móc nối". Dưới đây là một đoạn trích.
class CustomOrder
{
private:
static string workSymbol;
static void replaceRequest(MqlTradeRequest &request)
{
if(request.symbol == _Symbol && workSymbol != NULL)
{
request.symbol = workSymbol;
if(MQLInfoInteger(MQL_TESTER)
&& (request.type == ORDER_TYPE_BUY
|| request.type == ORDER_TYPE_SELL))
{
if(TU::Equal(request.price, SymbolInfoDouble(_Symbol, SYMBOL_ASK)))
request.price = SymbolInfoDouble(workSymbol, SYMBOL_ASK);
if(TU::Equal(request.price, SymbolInfoDouble(_Symbol, SYMBOL_BID)))
request.price = SymbolInfoDouble(workSymbol, SYMBOL_BID);
}
}
}
public:
static void setReplacementSymbol(const string replacementSymbol)
{
workSymbol = replacementSymbol;
}
static bool OrderSend(MqlTradeRequest &request, MqlTradeResult &result)
{
replaceRequest(request);
return ::OrderSend(request, result);
}
...
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
29
30
31
32
33
34
Xin lưu ý rằng phương thức làm việc chính replaceRequest
không chỉ thay thế biểu tượng mà còn thay thế giá Ask
và Bid
hiện tại. Điều này là do nhiều công cụ tùy chỉnh, chẳng hạn như biểu đồ Equivolume của chúng ta, có thời gian ảo khác với thời gian của biểu tượng nguyên mẫu thực tế. Do đó, giá của công cụ tùy chỉnh được mô phỏng bởi tester không đồng bộ với giá tương ứng của công cụ thực.
Hiện tượng này chỉ xảy ra trong tester. Khi giao dịch trực tuyến, biểu đồ biểu tượng tùy chỉnh sẽ được cập nhật (theo giá) đồng bộ với biểu tượng thực, mặc dù nhãn thanh sẽ khác nhau (một thanh M1 "nhân tạo" có thời lượng thực tế nhiều hơn hoặc ít hơn một phút, và thời gian đếm ngược của nó không phải là bội số của một phút). Do đó, việc chuyển đổi giá này chủ yếu là một biện pháp phòng ngừa để tránh bị requotes trong tester. Tuy nhiên, trong tester, chúng ta thường không cần thay thế biểu tượng, vì tester có thể giao dịch với biểu tượng tùy chỉnh (khác với máy chủ của nhà môi giới). Tiếp theo, chỉ để cho thú vị, chúng ta sẽ so sánh kết quả của các bài kiểm tra chạy cả với và không có thay thế ký tự.
Để giảm thiểu chỉnh sửa mã nguồn phía máy khách, các hàm toàn cục và macro dưới dạng sau được cung cấp (cho tất cả các phương thức CustomOrder
):
bool CustomOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
{
return CustomOrder::OrderSend((MqlTradeRequest)request, result);
}
#define OrderSend CustomOrderSend
2
3
4
5
6
Chúng cho phép chuyển hướng tự động tất cả các lệnh gọi hàm API tiêu chuẩn đến các phương thức của lớp CustomOrder
. Để thực hiện điều này, chỉ cần bao gồm CustomOrder.mqh
vào Expert Advisor và đặt biểu tượng làm việc, ví dụ, trong tham số WorkSymbol
:
#include <CustomOrder.mqh>
#include <Expert/Expert.mqh>
...
input string WorkSymbol = "";
int OnInit()
{
if(WorkSymbol != "")
{
CustomOrder::setReplacementSymbol(WorkSymbol);
// khởi tạo việc mở tab biểu đồ của biểu tượng làm việc (trong chế độ trực quan của tester)
MqlRates rates[1];
CopyRates(WorkSymbol, PERIOD_CURRENT, 0, 1, rates);
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Điều quan trọng là chỉ thị #include<CustomOrder.mqh>
phải là cái đầu tiên, trước các chỉ thị khác. Do đó, nó ảnh hưởng đến tất cả mã nguồn, bao gồm cả các thư viện tiêu chuẩn từ phân phối MetaTrader 5. Nếu không có biểu tượng thay thế nào được chỉ định, CustomOrder.mqh
được kết nối không có ảnh hưởng đến Expert Advisor và "minh bạch" chuyển điều khiển sang các hàm API tiêu chuẩn.
Bây giờ chúng ta đã sẵn sàng để kiểm tra ý tưởng giao dịch trên biểu tượng tùy chỉnh, bao gồm chính biểu tượng tùy chỉnh.
Áp dụng kỹ thuật được hiển thị ở trên, chúng ta sửa đổi Expert Advisor quen thuộc BandOsMaPro
, đổi tên nó thành BandOsMaCustom.mq5
. Hãy kiểm tra nó trên biểu đồ equivolume EURUSD với kích thước thanh 1000 tick thu được bằng cách sử dụng EqualVolumeBars.mq5
.
Chế độ tối ưu hóa hoặc kiểm tra được đặt thành giá OHLC M1 (các phương pháp chính xác hơn không có ý nghĩa vì chúng ta không tạo tick và cũng vì phiên bản này giao dịch ở giá của các thanh đã hình thành). Phạm vi ngày là toàn bộ năm 2021 và nửa đầu năm 2022. Tệp với các cài đặt BandOsMACustom.set
được đính kèm.
Trong cài đặt tester, bạn không nên quên chọn biểu tượng tùy chỉnh EURUSD_Eqv1000 và khung thời gian M1, vì chính trên đó các thanh equivolume được mô phỏng.
Khi tham số WorkSymbol
trống, Expert Advisor giao dịch biểu tượng tùy chỉnh. Dưới đây là kết quả:
Báo cáo của Tester khi giao dịch trên biểu đồ equivolume EURUSD_Eqv1000
Nếu tham số WorkSymbol
bằng EURUSD, Expert Advisor giao dịch cặp EURUSD, mặc dù nó hoạt động trên biểu đồ EURUSD_Eqv1000. Kết quả khác nhau nhưng không nhiều.
Báo cáo của Tester khi giao dịch EURUSD từ biểu đồ equivolume EURUSD_Eqv1000
Tuy nhiên, như đã đề cập ở đầu phần, có một cách dễ dàng hơn cho các Expert Advisor giao dịch dựa trên tín hiệu chỉ báo để hỗ trợ các biểu tượng tùy chỉnh. Để làm điều này, chỉ cần tạo các chỉ báo trên biểu tượng tùy chỉnh và đặt Expert Advisor trên biểu đồ của biểu tượng làm việc.
Chúng ta có thể dễ dàng triển khai tùy chọn này. Hãy gọi nó là BandOsMACustomSignal.mq5
.
Tệp tiêu đề CustomOrder.mqh
không còn cần thiết nữa. Thay vì tham số đầu vào WorkSymbol
, chúng ta thêm hai tham số mới:
input string SignalSymbol = "";
input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M1;
2
Chúng nên được truyền vào hàm tạo của lớp BandOsMaSignal
quản lý các chỉ báo. Trước đây, _Symbol
và _Period
được sử dụng ở khắp mọi nơi.
interface TradingSignal
{
virtual int signal(void);
virtual string symbol();
virtual ENUM_TIMEFRAMES timeframe();
};
class BandOsMaSignal: public TradingSignal
{
int hOsMA, hBands, hMA;
int direction;
const string _symbol;
const ENUM_TIMEFRAMES _timeframe;
public:
BandOsMaSignal(const string s, const ENUM_TIMEFRAMES tf,
const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price,
const int bands, const int shift, const double deviation,
const int period, const int x, ENUM_MA_METHOD method): _symbol(s), _timeframe(tf)
{
hOsMA = iOsMA(s, tf, fast, slow, signal, price);
hBands = iBands(s, tf, bands, shift, deviation, hOsMA);
hMA = iMA(s, tf, period, x, method, hOsMA);
direction = 0;
}
...
virtual string symbol() override
{
return _symbol;
}
virtual ENUM_TIMEFRAMES timeframe() override
{
return _timeframe;
}
}
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
29
30
31
32
33
34
35
Vì biểu tượng và khung thời gian cho tín hiệu giờ đây có thể khác với biểu tượng và chu kỳ của biểu đồ, chúng ta đã mở rộng giao diện TradingSignal
bằng cách thêm các phương thức đọc. Các giá trị thực được truyền vào hàm tạo trong OnInit
.
int OnInit()
{
...
strategy = new SimpleStrategy(
new BandOsMaSignal(SignalSymbol != "" ? SignalSymbol : _Symbol,
SignalSymbol != "" ? SignalTimeframe : _Period,
p.fast, p.slow, SignalOsMA, PriceOsMA,
BandsMA, BandsShift, BandsDeviation,
PeriodMA, ShiftMA, MethodMA),
Magic, StopLoss, Lots);
return INIT_SUCCEEDED;
}
2
3
4
5
6
7
8
9
10
11
12
Trong lớp SimpleStrategy
, phương thức trade
giờ đây kiểm tra sự xuất hiện của một thanh mới không theo biểu đồ hiện tại, mà theo các thuộc tính của tín hiệu.
virtual bool trade() override
{
// tìm kiếm tín hiệu một lần khi mở thanh của biểu tượng và khung thời gian mong muốn
if(lastBar == iTime(command[].symbol(), command[].timeframe(), 0)) return false;
int s = command[].signal(); // lấy tín hiệu
...
}
2
3
4
5
6
7
8
Để thực hiện một thí nghiệm so sánh với cùng cài đặt, Expert Advisor BandOsMACustomSignal.mq5
nên được khởi chạy trên EURUSD (bạn có thể sử dụng M1 hoặc khung thời gian khác), và EURUSD_Eqv1000 nên được chỉ định trong tham số SignalSymbol
. SignalTimeframe
nên được để mặc định bằng PERIOD_M1. Kết quả là, chúng ta sẽ nhận được một báo cáo tương tự.
Báo cáo của Tester khi giao dịch trên biểu đồ EURUSD dựa trên tín hiệu từ biểu tượng equivolume EURUSD_Eqv1000
Số lượng thanh và tick ở đây khác nhau vì EURUSD được chọn làm công cụ được kiểm tra chứ không phải biểu tượng tùy chỉnh EURUSD_Eqv1000.
Cả ba kết quả kiểm tra đều hơi khác nhau. Điều này là do việc "đóng gói" báo giá vào các thanh phút và sự không đồng bộ nhẹ của các chuyển động giá của công cụ gốc và công cụ tùy chỉnh. Kết quả nào chính xác hơn? Điều này, rất có thể, phụ thuộc vào hệ thống giao dịch cụ thể và các đặc điểm triển khai của nó. Trong trường hợp của Expert Advisor BandOsMa
của chúng ta với việc kiểm soát mở thanh, phiên bản giao dịch trực tiếp trên EURUSD_Eqv1000 nên có kết quả thực tế nhất. Về lý thuyết, quy tắc chung cho rằng trong số nhiều kiểm tra thay thế, cái đáng tin cậy nhất thường là cái ít lợi nhuận nhất, hầu như luôn được thỏa mãn.
Vì vậy, chúng ta đã phân tích một vài kỹ thuật để điều chỉnh Expert Advisor cho giao dịch trên các biểu tượng tùy chỉnh có nguyên mẫu trong số các biểu tượng làm việc của nhà môi giới. Tuy nhiên, tình huống này không phải là bắt buộc. Trong nhiều trường hợp, các biểu tượng tùy chỉnh được tạo dựa trên dữ liệu từ các hệ thống bên ngoài như sàn giao dịch tiền điện tử. Giao dịch trên chúng phải được thực hiện bằng API công khai của chúng với các hàm mạng MQL5.
Mô phỏng các loại biểu đồ đặc biệt với các biểu tượng tùy chỉnh
Nhiều nhà giao dịch sử dụng các loại biểu đồ đặc biệt, trong đó thời gian thực liên tục bị loại bỏ khỏi sự xem xét. Điều này không chỉ bao gồm các thanh equivolume và phạm vi bằng nhau, mà còn cả Renko, Point-And-Figure (PAF), Kagi, và các loại khác. Các biểu tượng tùy chỉnh cho phép các loại biểu đồ này được mô phỏng trong MetaTrader 5 bằng các biểu đồ khung thời gian M1 nhưng nên được xử lý cẩn thận khi kiểm tra các hệ thống giao dịch hơn là phân tích kỹ thuật.
Đối với các loại biểu đồ đặc biệt, thời gian mở thanh thực tế (chính xác đến mili giây) hầu như luôn không trùng khớp chính xác với phút mà thanh M1 sẽ được đánh dấu. Do đó, giá mở của một thanh tùy chỉnh khác với giá mở của thanh M1 của một biểu tượng tiêu chuẩn.
Hơn nữa, các giá OHLC khác cũng sẽ khác vì thời lượng thực tế của việc hình thành thanh M1 trên biểu đồ đặc biệt không bằng một phút. Ví dụ, 1000 tick cho một biểu đồ equivolume có thể tích lũy trong hơn 5 phút.
Giá đóng của một thanh tùy chỉnh cũng không tương ứng với thời gian đóng thực tế vì một thanh tùy chỉnh, về mặt kỹ thuật, là một thanh M1, tức là nó có thời lượng danh nghĩa là 1 phút.
Cần đặc biệt cẩn thận khi làm việc với các loại biểu đồ như Renko cổ điển hoặc PAF. Sự thật là các thanh đảo chiều của chúng có giá mở với một khoảng cách từ giá đóng của thanh trước đó. Do đó, giá mở trở thành một yếu tố dự đoán chuyển động giá trong tương lai.
Việc phân tích các biểu đồ này được cho là được thực hiện theo các thanh đã hình thành, tức là giá đặc trưng của chúng là giá đóng, tuy nhiên, khi làm việc theo thanh, tester chỉ cung cấp giá mở cho thanh hiện tại (thanh cuối cùng) (không có chế độ theo giá đóng). Ngay cả khi chúng ta lấy tín hiệu chỉ báo từ các thanh đã đóng (thường là từ thanh thứ 1), các giao dịch vẫn được thực hiện ở giá hiện tại của thanh thứ 0. Và ngay cả khi chúng ta chuyển sang chế độ tick, tester luôn tạo tick theo các quy tắc thông thường, dựa trên các điểm tham chiếu dựa trên cấu hình của mỗi thanh. Tester không tính đến cấu trúc và hành vi của các biểu đồ đặc biệt, mà chúng ta đang cố gắng mô phỏng trực quan với các thanh M1.
Giao dịch trong tester bằng các biểu tượng như vậy ở bất kỳ chế độ nào (theo giá mở, OHLC M1, hoặc theo tick) ảnh hưởng đến độ chính xác của kết quả: chúng quá lạc quan và có thể là nguồn gây ra kỳ vọng quá cao. Về vấn đề này, điều quan trọng là kiểm tra hệ thống giao dịch không chỉ trên một biểu đồ Renko hoặc PAF riêng lẻ, mà kết hợp với việc thực hiện lệnh trên một biểu tượng thực tế.
Các biểu tượng tùy chỉnh cũng có thể được sử dụng cho các khung thời gian giây hoặc biểu đồ tick. Trong trường hợp này, thời gian ảo cũng được tạo cho các thanh và tick, tách khỏi thời gian thực. Do đó, các biểu đồ như vậy rất phù hợp cho phân tích vận hành nhưng đòi hỏi sự chú ý bổ sung khi phát triển và kiểm tra các chiến lược giao dịch, đặc biệt là các chiến lược đa biểu tượng.
Một giải pháp thay thế cho bất kỳ biểu tượng tùy chỉnh nào là tính toán độc lập các mảng thanh và tick bên trong một Expert Advisor hoặc chỉ báo. Tuy nhiên, việc gỡ lỗi và trực quan hóa các cấu trúc như vậy đòi hỏi nỗ lực bổ sung.