Thêm, thay thế và xóa tick
API MQL5 cho phép bạn tạo lịch sử của một ký hiệu tùy chỉnh không chỉ ở cấp độ thanh mà còn ở cấp độ tick. Do đó, có thể đạt được độ chân thực cao hơn khi kiểm tra và tối ưu hóa các Expert Advisor, cũng như mô phỏng việc cập nhật biểu đồ của các ký hiệu tùy chỉnh theo thời gian thực, truyền các tick của bạn đến chúng. Tập hợp các tick được truyền vào hệ thống sẽ tự động được tính đến khi hình thành các thanh. Nói cách khác, không cần gọi các hàm từ phần trước hoạt động trên cấu trúc MqlRates
, nếu thông tin chi tiết hơn về biến động giá trong cùng khoảng thời gian được cung cấp dưới dạng tick, cụ thể là các mảng cấu trúc MqlTick
. Lợi thế duy nhất của báo giá theo thanh MqlRates
là hiệu suất và hiệu quả bộ nhớ.
Có hai hàm để thêm tick: CustomTicksAdd
và CustomTicksReplace
. Hàm đầu tiên thêm các tick tương tác đến cửa sổ Market Watch
(từ đó chúng được terminal tự động chuyển vào cơ sở dữ liệu tick) và tạo ra các sự kiện tương ứng trong các chương trình MQL. Hàm thứ hai ghi tick trực tiếp vào cơ sở dữ liệu tick.
int CustomTicksAdd(const string symbol, const MqlTick &ticks[], uint count = WHOLE_ARRAY)
Hàm CustomTicksAdd
thêm dữ liệu từ mảng ticks
vào lịch sử giá của một ký hiệu tùy chỉnh được chỉ định trong symbol
. Theo mặc định, nếu cài đặt count
bằng WHOLE_ARRAY
, toàn bộ mảng sẽ được thêm vào. Nếu cần, bạn có thể chỉ định một số nhỏ hơn và chỉ tải một phần của các tick.
Lưu ý rằng ký hiệu tùy chỉnh phải được chọn trong cửa sổ Market Watch
tại thời điểm gọi hàm. Đối với các ký hiệu không được chọn trong Market Watch
, bạn cần sử dụng hàm CustomTicksReplace
(xem thêm phía dưới).
Mảng dữ liệu tick phải được sắp xếp theo thời gian theo thứ tự tăng dần, tức là cần đáp ứng điều kiện sau: ticks[i].time_msc <= ticks[j].time_msc
cho mọi i < j
.
Hàm trả về số lượng tick được thêm vào hoặc -1 trong trường hợp có lỗi.
Hàm CustomTicksAdd
truyền các tick đến biểu đồ giống như cách chúng đến từ máy chủ của nhà môi giới. Thông thường, hàm này được áp dụng cho một hoặc nhiều tick. Trong trường hợp này, chúng được "phát" trong cửa sổ Market Watch
, từ đó được lưu vào cơ sở dữ liệu tick.
Tuy nhiên, khi một lượng dữ liệu lớn được truyền trong một lần gọi, hàm thay đổi hành vi để tiết kiệm tài nguyên. Nếu truyền hơn 256 tick, chúng được chia thành hai phần. Phần đầu tiên (lớn) được ghi trực tiếp vào cơ sở dữ liệu tick (như cách CustomTicksReplace
thực hiện). Phần thứ hai, gồm 128 tick cuối cùng (gần nhất), được truyền đến cửa sổ Market Watch
, sau đó được terminal lưu vào cơ sở dữ liệu.
Cấu trúc MqlTick
có hai trường chứa giá trị thời gian: time
(thời gian tick tính bằng giây) và time_msc
(thời gian tick tính bằng mili giây). Cả hai giá trị đều được tính từ ngày 01/01/1970. Trường time_msc
được điền (khác không) sẽ ưu tiên hơn time
. Lưu ý rằng time
được điền bằng giây dựa trên công thức tính lại time_msc / 1000
. Nếu trường time_msc
bằng 0, giá trị từ trường time
được sử dụng, và trường time_msc
lần lượt nhận giá trị mili giây từ công thức time * 1000
. Nếu cả hai trường đều bằng 0, thời gian máy chủ hiện tại (chính xác đến mili giây) được đưa vào tick.
Trong hai trường mô tả khối lượng, volume_real
có ưu tiên cao hơn volume
.
Tùy thuộc vào việc các trường nào khác được điền trong một phần tử mảng cụ thể (cấu trúc MqlTick
), hệ thống đặt các cờ cho tick được lưu trong trường flags
:
ticks[i].bid
—TICK_FLAG_BID
(tick thay đổi giá Bid)ticks[i].ask
—TICK_FLAG_ASK
(tick thay đổi giá Ask)ticks[i].last
—TICK_FLAG_LAST
(tick thay đổi giá giao dịch cuối cùng)ticks[i].volume
hoặcticks[i].volume_real
—TICK_FLAG_VOLUME
(tick thay đổi khối lượng)
Nếu giá trị của một trường nào đó nhỏ hơn hoặc bằng 0, cờ tương ứng không được ghi vào trường flags
.
Các cờ
TICK_FLAG_BUY
vàTICK_FLAG_SELL
không được thêm vào lịch sử của một ký hiệu tùy chỉnh.
Hàm CustomTicksReplace
thay thế hoàn toàn lịch sử giá của ký hiệu tùy chỉnh trong khoảng thời gian được chỉ định bằng dữ liệu từ mảng được truyền vào.
int CustomTicksReplace(const string symbol, long from_msc, long to_msc, const MqlTick &ticks[], uint count = WHOLE_ARRAY)
Khoảng thời gian được thiết lập bởi các tham số from_msc
và to_msc
, tính bằng mili giây kể từ 01/01/1970. Cả hai giá trị đều được bao gồm trong khoảng.
Mảng ticks
phải được sắp xếp theo thứ tự thời gian đến của các tick, tương ứng với thời gian tăng dần, hoặc chính xác hơn là không giảm, vì các tick có cùng thời gian thường xuất hiện liên tiếp trong một luồng với độ chính xác mili giây.
Tham số count
có thể được sử dụng để xử lý một phần của mảng.
Các tick được thay thế tuần tự theo ngày cho đến thời gian được chỉ định trong to_msc
, hoặc cho đến khi xảy ra lỗi trong thứ tự tick. Ngày đầu tiên trong phạm vi được chỉ định được xử lý trước, sau đó đến ngày tiếp theo, và cứ thế. Ngay khi phát hiện sự không khớp giữa thời gian tick và thứ tự tăng dần (không giảm), quá trình thay thế tick dừng lại ở ngày hiện tại. Trong trường hợp này, các tick của các ngày trước đó sẽ được thay thế thành công, trong khi ngày hiện tại (tại thời điểm tick sai) và tất cả các ngày còn lại trong khoảng được chỉ định sẽ không thay đổi. Hàm sẽ trả về -1, với mã lỗi trong _LastError
là 0 ("không có lỗi").
Nếu mảng ticks
không có dữ liệu cho một số khoảng thời gian trong khoảng tổng quát giữa from_msc
và to_msc
(bao gồm), thì sau khi thực thi hàm, lịch sử của ký hiệu tùy chỉnh sẽ có một khoảng trống tương ứng với dữ liệu bị thiếu.
Nếu không có dữ liệu trong cơ sở dữ liệu tick trong khoảng thời gian được chỉ định, CustomTicksReplace
sẽ thêm các tick từ mảng ticks
vào đó.
Hàm CustomTicksDelete
có thể được sử dụng để xóa tất cả các tick trong khoảng thời gian được chỉ định.
int CustomTicksDelete(const string symbol, long from_msc, long to_msc)
Tên của ký hiệu tùy chỉnh đang được chỉnh sửa được đặt trong tham số symbol
, và khoảng thời gian cần xóa được thiết lập bởi các tham số from_msc
và to_msc
(bao gồm), tính bằng mili giây.
Hàm trả về số lượng tick đã xóa hoặc -1 trong trường hợp có lỗi.
Chú ý! Việc xóa tick bằng
CustomTicksDelete
dẫn đến việc tự động xóa các thanh tương ứng! Tuy nhiên, việc gọiCustomRatesDelete
, tức là xóa các thanh, không xóa các tick!
Để nắm vững tài liệu này trong thực tế, chúng ta sẽ giải quyết một số bài toán ứng dụng bằng cách sử dụng các hàm vừa được xem xét.
Trước tiên, hãy đề cập đến một nhiệm vụ thú vị như tạo một ký hiệu tùy chỉnh dựa trên một ký hiệu thực nhưng với mật độ tick giảm. Điều này sẽ tăng tốc độ kiểm tra và tối ưu hóa, cũng như giảm tiêu thụ tài nguyên (chủ yếu là RAM) so với chế độ dựa trên các tick thực, đồng thời duy trì chất lượng quá trình ở mức chấp nhận được, gần với lý tưởng.
Tăng tốc kiểm tra và tối ưu hóa
Các nhà giao dịch thường tìm cách tăng tốc quá trình tối ưu hóa và kiểm tra Expert Advisor. Trong số các giải pháp có thể, có những giải pháp rõ ràng mà bạn chỉ cần thay đổi cài đặt (khi được phép), và có những giải pháp tốn thời gian hơn đòi hỏi phải điều chỉnh Expert Advisor hoặc môi trường kiểm tra.
Trong số các giải pháp thuộc loại đầu tiên có:
· Giảm không gian tối ưu hóa bằng cách loại bỏ một số tham số hoặc giảm bước của chúng;
· Giảm thời gian tối ưu hóa;
· Chuyển sang chế độ mô phỏng tick chất lượng thấp hơn (ví dụ, từ tick thực sang OHLC M1);
· Bật tính toán lợi nhuận bằng điểm thay vì tiền;
· Nâng cấp máy tính;
· Sử dụng MQL Cloud hoặc các máy tính mạng cục bộ bổ sung.Trong số các giải pháp liên quan đến phát triển thuộc loại thứ hai có:
· Phân tích mã, dựa trên đó bạn có thể loại bỏ "điểm nghẽn" trong mã;
· Nếu có thể, sử dụng tính toán chỉ báo tiết kiệm tài nguyên, tức là không có chỉ thị#property tester_everytick_calculate
;
· Chuyển các thuật toán chỉ báo (nếu được sử dụng) trực tiếp vào mã Expert Advisor: các lệnh gọi chỉ báo gây ra một số chi phí phụ;
· Loại bỏ đồ họa và đối tượng;
· Lưu trữ tính toán, nếu có thể;
· Giảm số lượng vị thế mở đồng thời và lệnh đã đặt (việc tính toán trên mỗi tick có thể trở nên đáng chú ý khi số lượng lớn);
· Ảo hóa hoàn toàn các thanh toán, lệnh, giao dịch và vị thế: cơ chế kế toán tích hợp, do tính linh hoạt, hỗ trợ đa tiền tệ và các tính năng khác, có chi phí riêng, có thể được loại bỏ bằng cách thực hiện các hành động tương tự trong mã MQL5 (mặc dù tùy chọn này tốn thời gian nhất).Việc giảm mật độ tick thuộc về một loại giải pháp trung gian: nó đòi hỏi việc tạo lập trình một ký hiệu tùy chỉnh nhưng không ảnh hưởng đến mã nguồn của Expert Advisor.
Một ký hiệu tùy chỉnh với các tick giảm sẽ được tạo bởi tập lệnh CustomSymbolFilterTicks.mq5
. Công cụ ban đầu sẽ là ký hiệu hoạt động của biểu đồ mà tập lệnh được khởi chạy. Trong các tham số đầu vào, bạn có thể chỉ định thư mục cho ký hiệu tùy chỉnh và ngày bắt đầu xử lý lịch sử. Theo mặc định, nếu không có ngày nào được đưa ra, phép tính được thực hiện cho 120 ngày qua.
input string CustomPath = "MQL5Book\\Part7"; // Thư mục Ký hiệu Tùy chỉnh
input datetime _Start; // Bắt đầu (mặc định: 120 ngày trước)
2
Tên của ký hiệu được tạo từ tên của công cụ nguồn và hậu tố .TckFltr
. Sau này chúng ta sẽ thêm vào đó ký hiệu của phương pháp giảm tick.
string CustomSymbol = _Symbol + ".TckFltr";
const uint DailySeconds = 60 * 60 * 24;
datetime Start = _Start == 0 ? TimeCurrent() - DailySeconds * 120 : _Start;
2
3
Để thuận tiện, trong trình xử lý OnStart
, có thể xóa một bản sao trước đó của ký hiệu nếu nó đã tồn tại.
void OnStart()
{
bool custom = false;
if(PRTF(SymbolExist(CustomSymbol, custom)) && custom)
{
if(IDYES == MessageBox(StringFormat("Delete existing custom symbol '%s'?", CustomSymbol),
"Please, confirm", MB_YESNO))
{
SymbolSelect(CustomSymbol, false);
CustomRatesDelete(CustomSymbol, 0, LONG_MAX);
CustomTicksDelete(CustomSymbol, 0, LONG_MAX);
CustomSymbolDelete(CustomSymbol);
}
else
{
return;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Tiếp theo, với sự đồng ý của người dùng, một ký hiệu được tạo ra. Lịch sử được điền dữ liệu tick trong hàm phụ trợ GenerateTickData
. Nếu thành công, tập lệnh thêm ký hiệu mới vào Market Watch
và mở biểu đồ.
if(IDYES == MessageBox(StringFormat("Create new custom symbol '%s'?", CustomSymbol),
"Please, confirm", MB_YESNO))
{
if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol)))
{
CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Prunned ticks by " + EnumToString(Mode));
if(GenerateTickData())
{
SymbolSelect(CustomSymbol, true);
ChartOpen(CustomSymbol, PERIOD_H1);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Hàm GenerateTickData
xử lý các tick trong một vòng lặp theo từng phần, mỗi ngày. Các tick mỗi ngày được yêu cầu bằng cách gọi CopyTicksRange
. Sau đó, chúng cần được giảm theo một cách nào đó, được thực hiện bởi lớp TickFilter
mà chúng ta sẽ trình bày dưới đây. Cuối cùng, mảng tick được thêm vào lịch sử ký hiệu tùy chỉnh bằng CustomTicksReplace
.
bool GenerateTickData()
{
bool result = true;
datetime from = Start / DailySeconds * DailySeconds; // làm tròn lên đến đầu ngày
ulong read = 0, written = 0;
uint day = 0;
const uint total = (uint)((TimeCurrent() - from) / DailySeconds + 1);
MqlTick array[];
while(!IsStopped() && from < TimeCurrent())
{
Comment(TimeToString(from, TIME_DATE), " ", day++, "/", total);
const int r = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL,
from * 1000L, (from + DailySeconds) * 1000L - 1);
if(r < 0)
{
Alert("Error reading ticks at ", TimeToString(from, TIME_DATE));
result = false;
break;
}
read += r;
if(r > 0)
{
const int t = TickFilter::filter(Mode, array);
const int w = CustomTicksReplace(CustomSymbol,
from * 1000L, (from + DailySeconds) * 1000L - 1, array);
if(w <= 0)
{
Alert("Error writing custom ticks at ", TimeToString(from, TIME_DATE));
result = false;
break;
}
written += w;
}
from += DailySeconds;
}
if(read > 0)
{
PrintFormat("Done ticks - read: %lld, written: %lld, ratio: %.1f%%",
read, written, written * 100.0 / read);
}
Comment("");
return 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
35
36
37
38
39
40
41
42
43
44
45
46
47
Kiểm soát lỗi và đếm số tick đã xử lý được triển khai ở tất cả các giai đoạn. Cuối cùng, chúng ta xuất ra nhật ký số lượng tick ban đầu và còn lại, cũng như hệ số "nén".
Bây giờ hãy chuyển trực tiếp đến kỹ thuật giảm tick. Rõ ràng, có thể có nhiều cách tiếp cận, mỗi cách phù hợp hơn hoặc kém hơn với một chiến lược giao dịch cụ thể. Chúng ta sẽ cung cấp 3 phiên bản cơ bản được kết hợp trong lớp TickFilter
(TickFilter.mqh
). Ngoài ra, để hoàn thiện bức tranh, chế độ sao chép tick mà không giảm cũng được hỗ trợ.
Do đó, các chế độ sau được triển khai trong lớp:
- Không giảm
- Bỏ qua các chuỗi tick có thay đổi giá đơn điệu mà không đảo chiều (kiểu "zig-zag")
- Bỏ qua dao động giá trong phạm vi spread
- Chỉ ghi lại các tick có cấu hình fractal khi giá
Bid
hoặcAsk
đại diện cho cực trị giữa hai tick liền kề
Các chế độ này được mô tả dưới dạng các phần tử của liệt kê FILTER_MODE
.
class TickFilter
{
public:
enum FILTER_MODE
{
NONE,
SEQUENCE,
FLUTTER,
FRACTALS,
};
...
2
3
4
5
6
7
8
9
10
11
Mỗi chế độ được triển khai bởi một phương thức tĩnh riêng biệt nhận vào một mảng tick cần được làm thưa. Việc chỉnh sửa mảng được thực hiện tại chỗ (không phân bổ mảng đầu ra mới).
static int filterBySequences(MqlTick &data[]);
static int filterBySpreadFlutter(MqlTick &data[]);
static int filterByFractals(MqlTick &data[]);
2
3
Tất cả các phương thức trả về số lượng tick còn lại (kích thước mảng đã giảm).
Để thống nhất việc thực thi quy trình ở các chế độ khác nhau, phương thức filter
được cung cấp. Đối với chế độ NONE
, mảng data
giữ nguyên.
static int filter(FILTER_MODE mode, MqlTick &data[])
{
switch(mode)
{
case SEQUENCE: return filterBySequences(data);
case FLUTTER: return filterBySpreadFlutter(data);
case FRACTALS: return filterByFractals(data);
}
return ArraySize(data);
}
2
3
4
5
6
7
8
9
10
Ví dụ, đây là cách lọc theo chuỗi tick đơn điệu được triển khai trong phương thức filterBySequences
.
static int filterBySequences(MqlTick &data[])
{
const int size = ArraySize(data);
if(size < 3) return size;
int index = 2;
bool dirUp = data[1].bid - data[0].bid + data[1].ask - data[0].ask > 0;
for(int i = 2; i < size; i++)
{
if(dirUp)
{
if(data[i].bid - data[i - 1].bid + data[i].ask - data[i - 1].ask < 0)
{
dirUp = false;
data[index++] = data[i];
}
}
else
{
if(data[i].bid - data[i - 1].bid + data[i].ask - data[i - 1].ask > 0)
{
dirUp = true;
data[index++] = data[i];
}
}
}
return ArrayResize(data, index);
}
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
Và đây là cách làm thưa fractal trông như thế nào.
static int filterByFractals(MqlTick &data[])
{
int index = 1;
const int size = ArraySize(data);
if(size < 3) return size;
for(int i = 1; i < size - 2; i++)
{
if((data[i].bid < data[i - 1].bid && data[i].bid < data[i + 1].bid)
|| (data[i].ask > data[i - 1].ask && data[i].ask > data[i + 1].ask))
{
data[index++] = data[i];
}
}
return ArrayResize(data, index);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hãy lần lượt tạo một ký hiệu tùy chỉnh cho EURUSD ở một số chế độ giảm mật độ tick và so sánh hiệu suất của chúng, tức là mức độ "nén", tốc độ kiểm tra nhanh như thế nào, và hiệu suất giao dịch của Expert Advisor thay đổi ra sao.
Ví dụ, việc làm thưa các chuỗi tick mang lại kết quả sau (cho lịch sử một năm rưỡi trên MQ Demo).
Create new custom symbol 'EURUSD.TckFltr-SE'?
Fixing SYMBOL_TRADE_TICK_VALUE: 0.0 <<< 1.0
true SYMBOL_TRADE_TICK_VALUE 1.0 -> SUCCESS (0)
Fixing SYMBOL_TRADE_TICK_SIZE: 0.0 <<< 1e-05
true SYMBOL_TRADE_TICK_SIZE 1e-05 -> SUCCESS (0)
Number of found discrepancies: 2
Fixed
Done ticks - read: 31553509, written: 16927376, ratio: 53.6%
2
3
4
5
6
7
8
Đối với các chế độ làm mịn dao động và fractal, các chỉ số khác nhau:
EURUSD.TckFltr-FL will be updated
Done ticks - read: 31568782, written: 22205879, ratio: 70.3%
...
Create new custom symbol 'EURUSD.TckFltr-FR'?
...
Done ticks - read: 31569519, written: 12732777, ratio: 40.3%
2
3
4
5
6
Để thực hiện các thí nghiệm giao dịch thực tế dựa trên các tick nén, chúng ta cần một Expert Advisor. Hãy sử dụng phiên bản đã điều chỉnh của BandOsMATicks.mq5
, trong đó, so với bản gốc, việc giao dịch trên mỗi tick đã được kích hoạt (trong phương thức SimpleStrategy::trade
, dòng if(lastBar == iTime(_Symbol, _Period, 0)) return false;
đã bị vô hiệu hóa), và các giá trị của chỉ báo tín hiệu được lấy từ thanh 0 và 1 (trước đây chỉ có các thanh đã hoàn thành 1 và 2).
Hãy chạy Expert Advisor với phạm vi ngày từ đầu năm 2021 đến ngày 1 tháng 6 năm 2022. Các cài đặt được đính kèm trong tệp MQL5/Presets/MQL5Book/BandOsMAticks.set
. Hành vi tổng thể của đường cong số dư trong tất cả các chế độ khá tương đồng.
Biểu đồ tổng hợp của số dư kiểm tra trong các chế độ khác nhau theo tick
Sự dịch chuyển ngang của các cực trị tương đương của các đường cong khác nhau là do biểu đồ báo cáo tiêu chuẩn không sử dụng thời gian mà sử dụng số lượng giao dịch làm tọa độ ngang, điều này rõ ràng khác nhau do độ chính xác của việc kích hoạt tín hiệu giao dịch cho các cơ sở tick khác nhau.
Sự khác biệt trong các chỉ số hiệu suất được thể hiện trong bảng sau (N - số lượng giao dịch, $ - lợi nhuận, PF - hệ số lợi nhuận, RF - hệ số phục hồi, DD - mức sụt giảm):
Chế độ | Tick | Thời gian (phút:giây.mili giây) | Bộ nhớ | N | $ | PF | RF | DD |
---|---|---|---|---|---|---|---|---|
Thực tế | 31002919 | 02:45.251 | 835 Mb | 962 | 166.24 | 1.32 | 2.88 | 54.99 |
Mô phỏng | 25808139 | 01:58.131 | 687 Mb | 928 | 171.94 | 1.34 | 3.44 | 47.64 |
OHLC M1 | 2084820 | 00:11.094 | 224 Mb | 856 | 193.52 | 1.39 | 3.97 | 46.55 |
Chuỗi | 16310236 | 01:24.784 | 559 Mb | 860 | 168.95 | 1.34 | 2.92 | 55.16 |
Dao động | 21362616 | 01:52.172 | 623 Mb | 920 | 179.75 | 1.37 | 3.60 | 47.28 |
Fractal | 12270854 | 01:04.756 | 430 Mb | 866 | 142.19 | 1.27 | 2.47 | 54.80 |
Chúng ta sẽ coi bài kiểm tra dựa trên các tick thực tế là đáng tin cậy nhất và đánh giá các bài kiểm tra còn lại dựa trên mức độ gần với bài kiểm tra này. Rõ ràng, chế độ OHLC M1 cho thấy tốc độ cao nhất và chi phí tài nguyên thấp hơn do mất đi đáng kể độ chính xác (chế độ tại giá mở không được xem xét). Nó thể hiện kết quả tài chính quá lạc quan.
Trong số ba chế độ với các tick được nén nhân tạo, "Chuỗi" là gần nhất với thực tế về tập hợp các chỉ số. Nó nhanh hơn 2 lần so với thực tế về thời gian và hiệu quả hơn 1.5 lần về mức tiêu thụ bộ nhớ. Chế độ "Dao động" dường như giữ nguyên số lượng giao dịch ban đầu tốt hơn. Chế độ fractal nhanh nhất và ít đòi hỏi bộ nhớ nhất, tất nhiên, tốn nhiều thời gian và tài nguyên hơn OHLC M1, nhưng nó không đánh giá quá cao điểm số giao dịch.
Hãy nhớ rằng các thuật toán giảm tick có thể hoạt động khác nhau hoặc ngược lại, cho kết quả kém với các chiến lược giao dịch, công cụ tài chính khác nhau, và thậm chí cả lịch sử tick của một nhà môi giới cụ thể. Hãy tiến hành nghiên cứu với Expert Advisor của bạn và trong môi trường làm việc của bạn.
Trong ví dụ thứ hai về làm việc với các ký hiệu tùy chỉnh, hãy xem xét một tính năng thú vị được cung cấp bởi việc truyền tick bằng CustomTicksAdd
.
Nhiều nhà giao dịch sử dụng bảng giao dịch — các chương trình với các điều khiển tương tác để thực hiện các hành động giao dịch tùy ý theo cách thủ công. Bạn phải thực hành làm việc với chúng chủ yếu trực tuyến vì tester áp đặt một số hạn chế. Trước hết, tester không hỗ trợ các sự kiện trên biểu đồ và các đối tượng. Điều này khiến các điều khiển ngừng hoạt động. Ngoài ra, trong tester, bạn không thể áp dụng các đối tượng tùy ý cho việc đánh dấu đồ họa.
Hãy thử giải quyết những vấn đề này.
Chúng ta có thể tạo một ký hiệu tùy chỉnh dựa trên các tick lịch sử ở chế độ chậm. Sau đó, biểu đồ của ký hiệu này sẽ trở thành một tương tự của tester trực quan.
Cách tiếp cận này có một số ưu điểm:
- Hành vi tiêu chuẩn của tất cả các sự kiện biểu đồ
- Ứng dụng tương tác và cài đặt các chỉ báo
- Ứng dụng tương tác và điều chỉnh các đối tượng
- Chuyển đổi khung thời gian ngay lập tức
- Kiểm tra trên lịch sử đến thời điểm hiện tại, bao gồm cả hôm nay (tester tiêu chuẩn không cho phép kiểm tra hôm nay)
Về điểm cuối cùng, chúng ta lưu ý rằng các nhà phát triển của MetaTrader 5 đã cố ý cấm kiểm tra giao dịch vào ngày cuối cùng (hiện tại), mặc dù đôi khi điều này cần thiết để nhanh chóng tìm lỗi (trong mã hoặc trong chiến lược giao dịch).
Việc sửa đổi giá ngay lập tức (ví dụ, tăng spread) cũng có tiềm năng thú vị.
Dựa trên biểu đồ của ký hiệu tùy chỉnh như vậy, sau này chúng ta có thể triển khai một trình giả lập giao dịch thủ công trên dữ liệu lịch sử.
Trình tạo ký hiệu sẽ là Expert Advisor không giao dịch CustomTester.mq5
. Trong các tham số đầu vào của nó, chúng ta sẽ cung cấp chỉ dẫn về vị trí của ký hiệu tùy chỉnh mới trong hệ thống phân cấp ký hiệu, ngày bắt đầu trong quá khứ để truyền tick (và xây dựng báo giá ký hiệu tùy chỉnh), cũng như khung thời gian cho biểu đồ, sẽ được tự động mở để kiểm tra trực quan.
input string CustomPath = "MQL5Book\\Part7"; // Thư mục Ký hiệu Tùy chỉnh
input datetime _Start; // Bắt đầu (mặc định thụt lùi 120 ngày)
input ENUM_TIMEFRAMES Timeframe = PERIOD_H1;
2
3
Tên của ký hiệu mới được xây dựng từ tên ký hiệu của biểu đồ hiện tại và hậu tố .Tester
.
string CustomSymbol = _Symbol + ".Tester";
Nếu ngày bắt đầu không được chỉ định trong các tham số, Expert Advisor sẽ thụt lùi 120 ngày từ ngày hiện tại.
const uint DailySeconds = 60 * 60 * 24;
datetime Start = _Start == 0 ? TimeCurrent() - DailySeconds * 120 : _Start;
2
Các tick sẽ được đọc từ lịch sử các tick thực của ký hiệu đang hoạt động theo lô cho cả ngày cùng một lúc. Con trỏ đến ngày đang được đọc được lưu trong biến Cursor
.
bool FirstCopy = true;
// thêm 1 ngày trước đó, vì nếu không biểu đồ sẽ không cập nhật ngay lập tức
datetime Cursor = (Start / DailySeconds - 1) * DailySeconds; // làm tròn tại ranh giới ngày
2
3
Các tick của một ngày cần tái hiện sẽ được yêu cầu trong mảng Ticks
, từ đó chúng sẽ được truyền thành các lô nhỏ có kích thước step
đến biểu đồ của ký hiệu tùy chỉnh.
MqlTick Ticks[]; // tick cho ngày "hiện tại" trong quá khứ
int Index = 0; // vị trí trong tick trong một ngày
int Step = 32; // tua nhanh 32 tick mỗi lần (mặc định)
int StepRestore = 0; // ghi nhớ tốc độ trong thời gian tạm dừng
long Chart = 0; // biểu đồ ký hiệu tùy chỉnh đã tạo
bool InitDone = false; // dấu hiệu hoàn tất khởi tạo
2
3
4
5
6
Để phát các tick ở tốc độ không đổi, hãy khởi động bộ đếm thời gian trong OnInit
.
void OnInit()
{
EventSetMillisecondTimer(100);
}
void OnTimer()
{
if(!GenerateData())
{
EventKillTimer();
}
}
2
3
4
5
6
7
8
9
10
11
12
Các tick sẽ được tạo bởi hàm GenerateData
. Ngay sau khi khởi chạy, khi cờ InitDone
được đặt lại, chúng ta sẽ cố gắng tạo một ký hiệu mới hoặc xóa các báo giá và tick cũ nếu ký hiệu tùy chỉnh đã tồn tại.
bool GenerateData()
{
if(!InitDone)
{
bool custom = false;
if(PRTF(SymbolExist(CustomSymbol, custom)) && custom)
{
if(IDYES == MessageBox(StringFormat("Clean up existing custom symbol '%s'?",
CustomSymbol), "Please, confirm", MB_YESNO))
{
PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX));
PRTF(CustomTicksDelete(CustomSymbol, 0, LONG_MAX));
Sleep(1000);
MqlRates rates[1];
MqlTick tcks[];
if(PRTF(CopyRates(CustomSymbol, PERIOD_M1, 0, 1, rates)) == 1
|| PRTF(CopyTicks(CustomSymbol, tcks) > 0))
{
Alert("Can't delete rates and Ticks, internal error");
ExpertRemove();
}
}
else
{
return false;
}
}
else if(!PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol)))
{
return false;
}
... // (A)
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
Tại điểm này, chúng ta sẽ bỏ qua một số phần tại (A) và quay lại điểm này sau.
Sau khi tạo ký hiệu, chúng ta chọn nó trong Market Watch
và mở một biểu đồ cho nó.
SymbolSelect(CustomSymbol, true);
Chart = ChartOpen(CustomSymbol, Timeframe);
... // (B)
ChartSetString(Chart, CHART_COMMENT, "Custom Tester");
ChartSetInteger(Chart, CHART_SHOW_OBJECT_DESCR, true);
ChartRedraw(Chart);
InitDone = true;
}
...
2
3
4
5
6
7
8
9
Một vài dòng (B) cũng bị thiếu ở đây; chúng liên quan đến các cải tiến trong tương lai, nhưng chưa cần thiết.
Nếu ký hiệu đã được tạo, chúng ta bắt đầu phát các tick theo lô gồm Step
tick, nhưng không quá 256. Giới hạn này liên quan đến đặc thù của hàm CustomTicksAdd
.
else
{
for(int i = 0; i <= (Step - 1) / 256; ++i)
if(Step > 0 && !GenerateTicks())
{
return false;
}
}
return true;
}
2
3
4
5
6
7
8
9
10
Hàm trợ giúp GenerateTicks
phát các tick theo lô gồm Step
tick (nhưng không quá 256), đọc chúng từ mảng hàng ngày Ticks
theo độ lệch Index
. Khi mảng trống hoặc chúng ta đã đọc đến cuối, chúng ta yêu cầu các tick của ngày tiếp theo bằng cách gọi FillTickBuffer
.
bool GenerateTicks()
{
if(Index >= ArraySize(Ticks)) // mảng hàng ngày trống hoặc đã đọc đến cuối
{
if(!FillTickBuffer()) return false; // điền mảng với các tick mỗi ngày
}
const int m = ArraySize(Ticks);
MqlTick array[];
const int n = ArrayCopy(array, Ticks, 0, Index, fmin(fmin(Step, 256), m));
if(n <= 0) return false;
ResetLastError();
if(CustomTicksAdd(CustomSymbol, array) != ArraySize(array) || _LastError != 0)
{
Print(_LastError); // trong trường hợp ERR_CUSTOM_TICKS_WRONG_ORDER (5310)
ExpertRemove();
}
Comment("Speed: ", (string)Step, " / ", STR_TIME_MSC(array[n - 1].time_msc));
Index += Step; // tiến lên 'Step' tick
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hàm FillTickBuffer
sử dụng CopyTicksRange
để hoạt động.
bool FillTickBuffer()
{
int r;
ArrayResize(Ticks, 0);
do
{
r = PRTF(CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL, Cursor * 1000L,
(Cursor + DailySeconds) * 1000L - 1));
if(r > 0 && FirstCopy)
{
// Lưu ý: lệnh gọi trước này chỉ cần thiết để hiển thị biểu đồ
// từ trạng thái "Đang chờ cập nhật"
PRTF(CustomTicksReplace(CustomSymbol, Cursor * 1000L,
(Cursor + DailySeconds) * 1000L - 1, Ticks));
FirstCopy = false;
r = 0;
}
Cursor += DailySeconds;
}
while(r == 0 && Cursor < TimeCurrent()); // bỏ qua các ngày không giao dịch
Index = 0;
return r > 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Khi Expert Advisor dừng lại, chúng ta cũng sẽ đóng biểu đồ phụ thuộc (để nó không bị trùng lặp khi khởi động lần sau).
void OnDeinit(const int)
{
if(Chart != 0)
{
ChartClose(Chart);
}
Comment("");
}
2
3
4
5
6
7
8
Tại thời điểm này, Expert Advisor có thể được coi là hoàn tất, nhưng có một vấn đề. Vấn đề là, vì lý do nào đó, các thuộc tính của một ký hiệu tùy chỉnh không được sao chép "nguyên trạng" từ ký hiệu làm việc gốc, ít nhất là trong triển khai hiện tại của API MQL5. Điều này áp dụng ngay cả với các thuộc tính rất quan trọng, như SYMBOL_TRADE_TICK_VALUE
, SYMBOL_TRADE_TICK_SIZE
. Nếu chúng ta in giá trị của các thuộc tính này ngay sau khi gọi CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol)
, chúng ta sẽ thấy các số không ở đó.
Để tổ chức việc kiểm tra các thuộc tính, so sánh và, nếu cần, chỉnh sửa, chúng ta đã viết một lớp đặc biệt CustomSymbolMonitor
(CustomSymbolMonitor.mqh
) kế thừa từ SymbolMonitor
. Bạn có thể tự nghiên cứu cấu trúc bên trong của nó, trong khi ở đây chúng ta chỉ trình bày giao diện công khai.
Các hàm tạo cho phép bạn tạo một bộ giám sát ký hiệu tùy chỉnh, chỉ định một ký hiệu làm việc mẫu (bằng tên trong chuỗi, hoặc từ đối tượng SymbolMonitor
) đóng vai trò là nguồn cài đặt.
class CustomSymbolMonitor: public SymbolMonitor
{
public:
CustomSymbolMonitor(); // mẫu - _Symbol
CustomSymbolMonitor(const string s, const SymbolMonitor *m = NULL);
CustomSymbolMonitor(const string s, const string other);
// đặt/thay thế ký hiệu mẫu
void inherit(const SymbolMonitor &m);
// sao chép tất cả thuộc tính từ ký hiệu mẫu theo thứ tự xuôi hoặc ngược
bool setAll(const bool reverseOrder = true, const int limit = UCHAR_MAX);
// kiểm tra tất cả thuộc tính so với mẫu, trả về số lượng chỉnh sửa
int verifyAll(const int limit = UCHAR_MAX);
// kiểm tra các thuộc tính được chỉ định với mẫu, trả về số lượng chỉnh sửa
int verify(const int &properties[]);
// sao chép các thuộc tính đã cho từ mẫu, trả về true nếu tất cả được áp dụng
bool set(const int &properties[]);
// sao chép thuộc tính cụ thể từ mẫu, trả về true nếu được áp dụng
template<typename E>
bool set(const E e);
bool set(const ENUM_SYMBOL_INFO_INTEGER property, const long value) const
{
return CustomSymbolSetInteger(name, property, value);
}
bool set(const ENUM_SYMBOL_INFO_DOUBLE property, const double value) const
{
return CustomSymbolSetDouble(name, property, value);
}
bool set(const ENUM_SYMBOL_INFO_STRING property, const string value) const
{
return CustomSymbolSetString(name, property, value);
}
};
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
Vì các ký hiệu tùy chỉnh, không giống như các ký hiệu tiêu chuẩn, cho phép bạn đặt thuộc tính của riêng mình, một bộ ba phương thức set
đã được thêm vào lớp. Đặc biệt, chúng được sử dụng để chuyển hàng loạt các thuộc tính của mẫu và kiểm tra sự thành công của các hành động này trong các phương thức khác của lớp.
Bây giờ chúng ta có thể quay lại trình tạo ký hiệu tùy chỉnh và đoạn mã nguồn của nó, như đã chỉ ra trước đó bởi bình luận (A).
// (A) kiểm tra các thuộc tính quan trọng và đặt chúng ở chế độ "thủ công"
SymbolMonitor sm; // _Symbol
CustomSymbolMonitor csm(CustomSymbol, &sm);
int props[] = {SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE};
const int d1 = csm.verify(props); // kiểm tra và cố gắng sửa
if(d1)
{
Print("Number of found discrepancies: ", d1); // số lượng chỉnh sửa
if(csm.verify(props)) // kiểm tra lại
{
Alert("Custom symbol can not be created, internal error!");
return false; // ký hiệu không thể sử dụng nếu không chỉnh sửa thành công
}
Print("Fixed");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bây giờ bạn có thể chạy Expert Advisor CustomTester.mq5
và quan sát cách các báo giá được hình thành động trong biểu đồ tự động mở cũng như cách các tick được chuyển tiếp từ lịch sử trong cửa sổ Market Watch
.
Tuy nhiên, điều này được thực hiện với tốc độ cố định 32 tick mỗi 0,1 giây. Sẽ rất mong muốn thay đổi tốc độ phát lại ngay lập tức theo yêu cầu của người dùng, cả tăng và giảm. Việc điều khiển như vậy có thể được tổ chức, ví dụ, từ bàn phím.
Do đó, bạn cần thêm trình xử lý OnChartEvent
. Như chúng ta biết, đối với sự kiện CHARTEVENT_KEYDOWN
, chương trình nhận mã của phím được nhấn trong tham số lparam
, và chúng ta chuyển nó đến hàm CheckKeys
(xem bên dưới). Một đoạn (C), liên quan chặt chẽ đến (B), đã phải tạm hoãn và chúng ta sẽ quay lại nó ngay sau đây.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
// (C)
if(id == CHARTEVENT_KEYDOWN) // các sự kiện này chỉ đến khi biểu đồ đang hoạt động!
{
CheckKeys(lparam);
}
}
2
3
4
5
6
7
8
9
Trong hàm CheckKeys
, chúng ta xử lý các phím "mũi tên lên" và "mũi tên xuống" để tăng và giảm tốc độ phát lại. Ngoài ra, phím "tạm dừng" cho phép tạm dừng hoàn toàn quá trình "kiểm tra" (truyền tick). Nhấn "tạm dừng" lần nữa sẽ tiếp tục công việc với cùng tốc độ.
void CheckKeys(const long key)
{
if(key == VK_DOWN)
{
Step /= 2;
if(Step > 0)
{
Print("Slow down: ", Step);
ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step);
}
else
{
Print("Paused");
ChartSetString(Chart, CHART_COMMENT, "Paused");
ChartRedraw(Chart);
}
}
else if(key == VK_UP)
{
if(Step == 0)
{
Step = 1;
Print("Resumed");
ChartSetString(Chart, CHART_COMMENT, "Resumed");
}
else
{
Step *= 2;
Print("Speed up: ", Step);
ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step);
}
}
else if(key == VK_PAUSE)
{
if(Step > 0)
{
StepRestore = Step;
Step = 0;
Print("Paused");
ChartSetString(Chart, CHART_COMMENT, "Paused");
ChartRedraw(Chart);
}
else
{
Step = StepRestore;
Print("Resumed");
ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step);
}
}
}
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
Mã mới có thể được kiểm tra trong thực tế sau khi đảm bảo rằng biểu đồ mà Expert Advisor hoạt động đang hoạt động. Hãy nhớ rằng các sự kiện bàn phím chỉ đến cửa sổ đang hoạt động. Đây là một vấn đề khác của tester của chúng ta.
Vì người dùng phải thực hiện các hành động giao dịch trên biểu đồ ký hiệu tùy chỉnh, cửa sổ trình tạo hầu như luôn ở chế độ nền. Việc chuyển sang cửa sổ trình tạo để tạm dừng dòng tick và sau đó tiếp tục không thực tế. Do đó, cần một cách nào đó để tổ chức điều khiển tương tác từ bàn phím trực tiếp từ cửa sổ ký hiệu tùy chỉnh.
Để làm điều này, một chỉ báo đặc biệt là phù hợp, mà chúng ta có thể tự động thêm vào cửa sổ ký hiệu tùy chỉnh khi mở. Chỉ báo sẽ chặn các sự kiện bàn phím trong cửa sổ của nó (cửa sổ với ký hiệu tùy chỉnh) và gửi chúng đến cửa sổ trình tạo.
Mã nguồn của chỉ báo được đính kèm trong tệp KeyboardSpy.mq5
. Tất nhiên, chỉ báo không có biểu đồ. Một cặp tham số đầu vào được dành để lấy ID biểu đồ HostID
, nơi các thông điệp nên được gửi và mã sự kiện tùy chỉnh EventID
, trong đó các sự kiện tương tác sẽ được đóng gói.
#property indicator_chart_window
#property indicator_plots 0
input long HostID;
input ushort EventID;
2
3
4
5
Công việc chính được thực hiện trong trình xử lý OnChartEvent
.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
EventChartCustom(HostID, EventID, lparam,
// điều này luôn là 0 khi bên trong iCustom
(double)(ushort)TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL),
sparam);
}
}
2
3
4
5
6
7
8
9
10
Lưu ý rằng tất cả các "phím nóng" chúng ta đã chọn đều đơn giản, nghĩa là chúng không sử dụng các phím tắt với các phím trạng thái bàn phím, như Ctrl
hoặc Shift
. Điều này được thực hiện bắt buộc vì bên trong các chỉ báo được tạo bằng lập trình (đặc biệt, thông qua iCustom
), trạng thái bàn phím không được đọc. Nói cách khác, việc gọi TerminalInfoInteger(TERMINAL_KEYSTATE_XYZ)
luôn trả về 0. Trong trình xử lý trên, chúng ta đã thêm nó chỉ để minh họa, để bạn có thể xác minh giới hạn này nếu muốn, bằng cách hiển thị các tham số đến ở "phía nhận".
Tuy nhiên, các lần nhấp đơn vào mũi tên và tạm dừng sẽ được chuyển bình thường đến biểu đồ cha, và điều đó đủ cho chúng ta. Điều duy nhất còn lại là tích hợp chỉ báo với Expert Advisor.
Trong đoạn (B) đã bỏ qua trước đó, trong quá trình khởi tạo trình tạo, chúng ta sẽ tạo một chỉ báo và thêm nó vào biểu đồ ký hiệu tùy chỉnh.
#define EVENT_KEY 0xDED // sự kiện tùy chỉnh
...
// (B)
const int handle = iCustom(CustomSymbol, Timeframe, "MQL5Book/p7/KeyboardSpy",
ChartID(), EVENT_KEY);
ChartIndicatorAdd(Chart, 0, handle);
2
3
4
5
6
Tiếp theo, trong đoạn (C), chúng ta sẽ đảm bảo việc nhận các thông điệp người dùng từ chỉ báo và chuyển chúng đến hàm CheckKeys
đã biết.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
// (C)
if(id == CHARTEVENT_CUSTOM + EVENT_KEY) // thông báo từ biểu đồ phụ thuộc khi nó đang hoạt động
{
CheckKeys(lparam); // xử lý "từ xa" các lần nhấn phím
}
else if(id == CHARTEVENT_KEYDOWN) // các sự kiện này chỉ được kích hoạt khi biểu đồ đang hoạt động!
{
CheckKeys(lparam); // xử lý tiêu chuẩn
}
}
2
3
4
5
6
7
8
9
10
11
12
Như vậy, tốc độ phát lại bây giờ có thể được điều khiển cả trên biểu đồ với Expert Advisor và trên biểu đồ của ký hiệu tùy chỉnh do nó tạo ra.
Với bộ công cụ mới, bạn có thể thử làm việc tương tác với một biểu đồ "sống trong quá khứ". Một bình luận với tốc độ phát lại hiện tại hoặc dấu tạm dừng được hiển thị trên đồ thị.
Trên biểu đồ với Expert Advisor, thời gian của các tick "hiện tại" được phát được hiển thị trong bình luận.
Một Expert Advisor tái tạo lịch sử các tick (và báo giá) của một ký hiệu thực tế
Về cơ bản, không có gì để người dùng làm trong cửa sổ này (trừ khi Expert Advisor bị xóa và việc tạo ký hiệu tùy chỉnh bị dừng lại). Quá trình dịch chuyển tick không hiển thị ở đây. Hơn nữa, vì Expert Advisor tự động mở một biểu đồ ký hiệu tùy chỉnh (nơi các báo giá lịch sử được cập nhật), chính biểu đồ này trở nên hoạt động. Để có được ảnh chụp màn hình trên, chúng ta đặc biệt cần phải chuyển nhanh sang biểu đồ gốc.
Vì vậy, hãy quay lại biểu đồ của ký hiệu tùy chỉnh. Việc nó được cập nhật mượt mà và dần dần trong quá khứ đã rất tuyệt vời, nhưng bạn không thể tiến hành các thí nghiệm giao dịch trên đó. Ví dụ, nếu bạn chạy bảng giao dịch thông thường của mình trên đó, các điều khiển của nó, mặc dù sẽ hoạt động theo hình thức, nhưng sẽ không thực hiện được giao dịch vì ký hiệu tùy chỉnh không tồn tại trên máy chủ, và do đó bạn sẽ nhận được lỗi. Tính năng này được quan sát trong bất kỳ chương trình nào không được điều chỉnh đặc biệt cho các ký hiệu tùy chỉnh. Hãy cùng xem một ví dụ về cách giao dịch với ký hiệu tùy chỉnh có thể được ảo hóa.
Thay vì một bảng giao dịch (để đơn giản hóa ví dụ, nhưng không mất tính tổng quát), chúng ta sẽ lấy làm cơ sở Expert Advisor đơn giản nhất, CustomOrderSend.mq5
, có thể thực hiện một số hành động giao dịch bằng các lần nhấn phím:
B
— mua theo thị trườngS
— bán theo thị trườngU
— đặt lệnh giới hạn muaL
— đặt lệnh giới hạn bánC
— đóng tất cả các vị thếD
— xóa tất cả các lệnhR
— xuất báo cáo giao dịch ra nhật ký Trong các tham số đầu vào của Expert Advisor, chúng ta sẽ đặt khối lượng của một giao dịch (mặc định là lô tối thiểu) và khoảng cách đến các mức dừng lỗ và chốt lời tính bằng điểm.
input double Volume; // Khối lượng (0 = lô tối thiểu)
input int Distance2SLTP = 0; // Khoảng cách đến SL/TP tính bằng điểm (0 = không có)
const double Lot = Volume == 0 ? SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) : Volume;
2
3
Nếu Distance2SLTP
được để bằng không, các mức bảo vệ sẽ không được đặt trong các lệnh thị trường, và các lệnh chờ sẽ không được hình thành. Khi Distance2SLTP
có giá trị khác không, nó được sử dụng làm khoảng cách từ giá hiện tại khi đặt lệnh chờ (lên hoặc xuống, tùy thuộc vào lệnh).
Xét đến các lớp đã được trình bày trước đó từ MqlTradeSync.mqh
, logic trên được chuyển đổi thành mã nguồn sau.
#include <MQL5Book/MqlTradeSync.mqh>
#define KEY_B 66
#define KEY_C 67
#define KEY_D 68
#define KEY_L 76
#define KEY_R 82
#define KEY_S 83
#define KEY_U 85
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
MqlTradeRequestSync request;
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
switch((int)lparam)
{
case KEY_B:
request.buy(Lot, 0,
Distance2SLTP ? ask - point * Distance2SLTP : Distance2SLTP,
Distance2SLTP ? ask + point * Distance2SLTP : Distance2SLTP);
break;
case KEY_S:
request.sell(Lot, 0,
Distance2SLTP ? bid + point * Distance2SLTP : Distance2SLTP,
Distance2SLTP ? bid - point * Distance2SLTP : Distance2SLTP);
break;
case KEY_U:
if(Distance2SLTP)
{
request.buyLimit(Lot, ask - point * Distance2SLTP);
}
break;
case KEY_L:
if(Distance2SLTP)
{
request.sellLimit(Lot, bid + point * Distance2SLTP);
}
break;
case KEY_C:
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
request.close(PositionGetTicket(i));
}
break;
case KEY_D:
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
request.remove(OrderGetTicket(i));
}
break;
case KEY_R:
// nên có gì đó ở đây...
break;
}
}
}
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
52
53
54
55
56
57
58
59
60
61
Như chúng ta thấy, cả các hàm API giao dịch tiêu chuẩn và các phương thức MqlTradeRequestSync
đều được sử dụng ở đây. Các phương thức sau, một cách gián tiếp, cũng gọi nhiều hàm tích hợp sẵn. Chúng ta cần làm cho Expert Advisor này giao dịch với một ký hiệu tùy chỉnh.
Ý tưởng đơn giản nhất, mặc dù tốn thời gian, là thay thế tất cả các hàm tiêu chuẩn bằng các hàm tương tự tự tạo, những hàm này sẽ đếm các lệnh, giao dịch, vị thế và thống kê tài chính trong một số cấu trúc. Tất nhiên, điều này chỉ khả thi trong trường hợp chúng ta có mã nguồn của Expert Advisor, mà mã này cần được điều chỉnh.
Một triển khai thử nghiệm của phương pháp này được thể hiện trong tệp đính kèm CustomTrade.mqh
. Bạn có thể tự làm quen với mã đầy đủ, vì trong khuôn khổ cuốn sách, chúng ta chỉ liệt kê các điểm chính.
Trước hết, chúng ta lưu ý rằng nhiều phép tính được thực hiện ở dạng đơn giản hóa, nhiều chế độ không được hỗ trợ, và việc kiểm tra toàn diện tính đúng đắn của dữ liệu không được thực hiện. Hãy sử dụng mã nguồn như một điểm khởi đầu cho các phát triển của riêng bạn.
Toàn bộ mã được bọc trong không gian tên CustomTrade
để tránh xung đột.
Các thực thể lệnh, giao dịch và vị thế được chính thức hóa thành các lớp tương ứng CustomOrder
, CustomDeal
và CustomPosition
. Tất cả đều là lớp kế thừa của lớp MonitorInterface<I,D,S> ::TradeState
. Hãy nhớ rằng lớp này đã tự động hỗ trợ việc hình thành các mảng thuộc tính kiểu số nguyên, số thực và chuỗi cho mỗi loại đối tượng và các bộ ba liệt kê cụ thể của nó. Ví dụ, CustomOrder
trông như thế này:
class CustomOrder: public MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,
ENUM_ORDER_PROPERTY_DOUBLE, ENUM_ORDER_PROPERTY_STRING>::TradeState
{
static long ticket; // bộ đếm lệnh và cung cấp mã số
static int done; // bộ đếm các lệnh đã thực hiện (lịch sử)
public:
CustomOrder(const ENUM_ORDER_TYPE type, const double volume, const string symbol)
{
_set(ORDER_TYPE, type);
_set(ORDER_TICKET, ++ticket);
_set(ORDER_TIME_SETUP, SymbolInfoInteger(symbol, SYMBOL_TIME));
_set(ORDER_TIME_SETUP_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
if(type <= ORDER_TYPE_SELL)
{
// TODO: chưa có thực thi trì hoãn
setDone(ORDER_STATE_FILLED);
}
else
{
_set(ORDER_STATE, ORDER_STATE_PLACED);
}
_set(ORDER_VOLUME_INITIAL, volume);
_set(ORDER_VOLUME_CURRENT, volume);
_set(ORDER_SYMBOL, symbol);
}
void setDone(const ENUM_ORDER_STATE state)
{
const string symbol = _get<string>(ORDER_SYMBOL);
_set(ORDER_TIME_DONE, SymbolInfoInteger(symbol, SYMBOL_TIME));
_set(ORDER_TIME_DONE_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
_set(ORDER_STATE, state);
++done;
}
bool isActive() const
{
return _get<long>(ORDER_TIME_DONE) == 0;
}
static int getDoneCount()
{
return done;
}
};
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
Lưu ý rằng trong môi trường ảo của thời gian "hiện tại" cũ, bạn không thể sử dụng hàm TimeCurrent
và thời gian cuối cùng được biết đến của ký hiệu tùy chỉnh SymbolInfoInteger(symbol, SYMBOL_TIME)
được lấy thay thế.
Trong quá trình giao dịch ảo, các đối tượng hiện tại và lịch sử của chúng được tích lũy trong các mảng của các lớp tương ứng.
AutoPtr<CustomOrder> orders[];
CustomOrder *selectedOrders[];
CustomOrder *selectedOrder = NULL;
AutoPtr<CustomDeal> deals[];
CustomDeal *selectedDeals[];
CustomDeal *selectedDeal = NULL;
AutoPtr<CustomPosition> positions[];
CustomPosition *selectedPosition = NULL;
2
3
4
5
6
7
8
Việc ẩn dụ chọn lọc các lệnh, giao dịch và vị thế là cần thiết để mô phỏng cách tiếp cận tương tự trong các hàm tích hợp sẵn. Đối với chúng, có các bản sao trong không gian tên CustomTrade
thay thế các bản gốc bằng các chỉ thị thay thế macro.
#define HistorySelect CustomTrade::MT5HistorySelect
#define HistorySelectByPosition CustomTrade::MT5HistorySelectByPosition
#define PositionGetInteger CustomTrade::MT5PositionGetInteger
#define PositionGetDouble CustomTrade::MT5PositionGetDouble
#define PositionGetString CustomTrade::MT5PositionGetString
#define PositionSelect CustomTrade::MT5PositionSelect
#define PositionSelectByTicket CustomTrade::MT5PositionSelectByTicket
#define PositionsTotal CustomTrade::MT5PositionsTotal
#define OrdersTotal CustomTrade::MT5OrdersTotal
#define PositionGetSymbol CustomTrade::MT5PositionGetSymbol
#define PositionGetTicket CustomTrade::MT5PositionGetTicket
#define HistoryDealsTotal CustomTrade::MT5HistoryDealsTotal
#define HistoryOrdersTotal CustomTrade::MT5HistoryOrdersTotal
#define HistoryDealGetTicket CustomTrade::MT5HistoryDealGetTicket
#define HistoryOrderGetTicket CustomTrade::MT5HistoryOrderGetTicket
#define HistoryDealGetInteger CustomTrade::MT5HistoryDealGetInteger
#define HistoryDealGetDouble CustomTrade::MT5HistoryDealGetDouble
#define HistoryDealGetString CustomTrade::MT5HistoryDealGetString
#define HistoryOrderGetDouble CustomTrade::MT5HistoryOrderGetDouble
#define HistoryOrderGetInteger CustomTrade::MT5HistoryOrderGetInteger
#define HistoryOrderGetString CustomTrade::MT5HistoryOrderGetString
#define OrderSend CustomTrade::MT5OrderSend
#define OrderSelect CustomTrade::MT5OrderSelect
#define HistoryOrderSelect CustomTrade::MT5HistoryOrderSelect
#define HistoryDealSelect CustomTrade::MT5HistoryDealSelect
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Ví dụ, đây là cách hàm MT5HistorySelectByPosition
được triển khai.
bool MT5HistorySelectByPosition(long id)
{
ArrayResize(selectedOrders, 0);
ArrayResize(selectedDeals, 0);
for(int i = 0; i < ArraySize(orders); i++)
{
CustomOrder *ptr = orders[i][];
if(!ptr.isActive())
{
if(ptr._get<long>(ORDER_POSITION_ID) == id)
{
PUSH(selectedOrders, ptr);
}
}
}
for(int i = 0; i < ArraySize(deals); i++)
{
CustomDeal *ptr = deals[i][];
if(ptr._get<long>(DEAL_POSITION_ID) == id)
{
PUSH(selectedDeals, ptr);
}
}
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
Như bạn thấy, tất cả các hàm của nhóm này có tiền tố MT5, để mục đích kép của chúng được rõ ràng ngay lập tức và dễ phân biệt với các hàm của nhóm thứ hai.
Nhóm thứ hai của các hàm trong không gian tên CustomTrade
thực hiện các hành động tiện ích: kiểm tra và cập nhật trạng thái của các lệnh, giao dịch và vị thế, tạo mới và xóa các đối tượng cũ theo tình huống. Đặc biệt, chúng bao gồm các hàm CheckPositions
và CheckOrders
, có thể được gọi theo bộ đếm thời gian hoặc phản hồi hành động của người dùng. Nhưng bạn không cần làm điều này nếu sử dụng một cặp hàm khác được thiết kế để hiển thị trạng thái hiện tại và lịch sử của tài khoản giao dịch ảo:
string ReportTradeState()
trả về văn bản nhiều dòng với danh sách các vị thế mở và lệnh đã đặtvoid PrintTradeHistory()
hiển thị lịch sử lệnh và giao dịch trong nhật ký
Các hàm này tự động gọi CheckPositions
và CheckOrders
để cung cấp cho bạn thông tin cập nhật.
Ngoài ra, có một hàm để trực quan hóa các vị thế và lệnh đang hoạt động trên biểu đồ dưới dạng các đối tượng: DisplayTrades
.
Tệp tiêu đề CustomTrade.mqh
nên được bao gồm trong Expert Advisor trước các tiêu đề khác để thay thế macro có hiệu lực trên tất cả các dòng mã nguồn tiếp theo.
#include <MQL5Book/CustomTrade.mqh>
#include <MQL5Book/MqlTradeSync.mqh>
2
Bây giờ, thuật toán CustomOrderSend.mq5
ở trên có thể bắt đầu "giao dịch" trong môi trường ảo dựa trên ký hiệu tùy chỉnh hiện tại (không yêu cầu máy chủ hoặc tester tiêu chuẩn) mà không cần bất kỳ thay đổi nào thêm.
Để hiển thị trạng thái nhanh chóng, chúng ta sẽ khởi động một bộ đếm thời gian thứ hai và định kỳ thay đổi bình luận, cũng như hiển thị các đối tượng đồ họa.
int OnInit()
{
EventSetTimer(1);
return INIT_SUCCEEDED;
}
void OnTimer()
{
Comment(CustomTrade::ReportTradeState());
CustomTrade::DisplayTrades();
}
2
3
4
5
6
7
8
9
10
11
Để xây dựng báo cáo bằng cách nhấn 'R', chúng ta thêm trình xử lý OnChartEvent
.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
switch((int)lparam)
{
...
case KEY_R:
CustomTrade::PrintTradeHistory();
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Cuối cùng, mọi thứ đã sẵn sàng để thử nghiệm gói phần mềm mới trong thực tế.
Chạy trình tạo ký hiệu tùy chỉnh CustomTester.mq5
trên EURUSD. Trên biểu đồ "EURUSD.Tester" mở ra, chạy CustomOrderSend.mq5
và bắt đầu giao dịch. Dưới đây là hình ảnh của quá trình kiểm tra.
Giao dịch ảo trên biểu đồ ký hiệu tùy chỉnh
Ở đây bạn có thể thấy hai vị thế mua mở (với các mức bảo vệ) và một lệnh bán giới hạn đang chờ.
Sau một thời gian, một trong các vị thế được đóng (được biểu thị dưới đây bằng đường nét đứt màu xanh với mũi tên), và một lệnh bán đang chờ được kích hoạt (đường màu đỏ với mũi tên), dẫn đến hình ảnh sau.
Giao dịch ảo trên biểu đồ ký hiệu tùy chỉnh
Sau khi đóng tất cả các vị thế (một số bằng chốt lời, phần còn lại bằng lệnh của người dùng), một báo cáo được yêu cầu bằng cách nhấn 'R'.
History Orders:
(1) #1 ORDER_TYPE_BUY 2022.02.15 01:20:50 -> 2022.02.15 01:20:50 L=0.01 @ 1.1306
(4) #2 ORDER_TYPE_SELL_LIMIT 2022.02.15 02:34:29 -> 2022.02.15 18:10:17 L=0.01 @ 1.13626 [sell limit]
(2) #3 ORDER_TYPE_BUY 2022.02.15 10:08:20 -> 2022.02.15 10:08:20 L=0.01 @ 1.13189
(3) #4 ORDER_TYPE_BUY 2022.02.15 15:01:26 -> 2022.02.15 15:01:26 L=0.01 @ 1.13442
(1) #5 ORDER_TYPE_SELL 2022.02.15 15:35:43 -> 2022.02.15 15:35:43 L=0.01 @ 1.13568
(2) #6 ORDER_TYPE_SELL 2022.02.16 09:39:17 -> 2022.02.16 09:39:17 L=0.01 @ 1.13724
(4) #7 ORDER_TYPE_BUY 2022.02.16 23:31:15 -> 2022.02.16 23:31:15 L=0.01 @ 1.13748
(3) #8 ORDER_TYPE_SELL 2022.02.16 23:31:15 -> 2022.02.16 23:31:15 L=0.01 @ 1.13742
Deals:
(1) #1 [#1] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 01:20:50 L=0.01 @ 1.1306 = 0.00
(2) #2 [#3] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 10:08:20 L=0.01 @ 1.13189 = 0.00
(3) #3 [#4] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 15:01:26 L=0.01 @ 1.13442 = 0.00
(1) #4 [#5] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.15 15:35:43 L=0.01 @ 1.13568 = 5.08 [tp]
(4) #5 [#2] DEAL_TYPE_SELL DEAL_ENTRY_IN 2022.02.15 18:10:17 L=0.01 @ 1.13626 = 0.00
(2) #6 [#6] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.16 09:39:17 L=0.01 @ 1.13724 = 5.35 [tp]
(4) #7 [#7] DEAL_TYPE_BUY DEAL_ENTRY_OUT 2022.02.16 23:31:15 L=0.01 @ 1.13748 = -1.22
(3) #8 [#8] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.16 23:31:15 L=0.01 @ 1.13742 = 3.00
Total: 12.21, Trades: 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dấu ngoặc đơn biểu thị định danh vị thế và dấu ngoặc vuông biểu thị mã số của các lệnh cho các giao dịch tương ứng (mã số của cả hai loại đều có tiền tố "#").
Swap và hoa hồng không được tính ở đây. Việc tính toán chúng có thể được thêm vào.
Chúng ta sẽ xem xét một ví dụ khác về làm việc với các tick của ký hiệu tùy chỉnh trong phần về đặc điểm giao dịch ký hiệu tùy chỉnh. Chúng ta sẽ nói về việc tạo biểu đồ equivolume.