Theo dõi thay đổi môi trường giao dịch
Trong phần trước liên quan đến sự kiện OnTrade
, chúng ta đã đề cập rằng một số cách tiếp cận lập trình chiến lược giao dịch có thể yêu cầu bạn chụp ảnh môi trường và so sánh chúng với nhau theo thời gian. Đây là một thực hành phổ biến khi sử dụng OnTrade
nhưng nó cũng có thể được kích hoạt theo lịch trình, trên mỗi thanh, hoặc thậm chí mỗi tick. Các lớp giám sát của chúng ta có thể đọc các thuộc tính của lệnh, giao dịch và vị thế nhưng thiếu khả năng lưu trạng thái. Trong phần này, chúng ta sẽ trình bày một trong những tùy chọn lưu trữ môi trường giao dịch.
Các thuộc tính của tất cả các đối tượng giao dịch được chia theo loại thành ba nhóm: số nguyên, số thực và chuỗi. Mỗi lớp đối tượng có nhóm riêng (ví dụ, đối với lệnh, các thuộc tính số nguyên được mô tả trong liệt kê ENUM_ORDER_PROPERTY_INTEGER
, và đối với vị thế là ENUM_POSITION_PROPERTY_INTEGER
), nhưng bản chất của việc phân chia là giống nhau. Do đó, chúng ta sẽ giới thiệu liệt kê PROP_TYPE
, với sự trợ giúp của nó, có thể mô tả một thuộc tính đối tượng thuộc loại nào. Sự tổng quát hóa này xuất hiện tự nhiên vì các cơ chế lưu trữ và xử lý các thuộc tính cùng loại phải giống nhau, bất kể thuộc tính đó thuộc về lệnh, vị thế hay giao dịch.
enum PROP_TYPE
{
PROP_TYPE_INTEGER,
PROP_TYPE_DOUBLE,
PROP_TYPE_STRING,
};
2
3
4
5
6
Mảng là cách đơn giản nhất để lưu trữ giá trị thuộc tính. Rõ ràng, do có ba loại cơ bản, chúng ta sẽ cần ba mảng khác nhau. Hãy mô tả chúng bên trong một lớp mới TradeState
được lồng trong MonitorInterface
(TradeBaseMonitor.mqh
).
Mẫu cơ bản MonitorInterface<I,D,S>
tạo thành nền tảng của tất cả các lớp giám sát ứng dụng (OrderMonitor
, DealMonitor
, PositionMonitor
). Các loại I, D và S ở đây tương ứng với các liệt kê cụ thể của thuộc tính số nguyên, số thực và chuỗi.
Việc bao gồm cơ chế lưu trữ trong giám sát cơ bản là khá hợp lý, đặc biệt vì bộ nhớ cache thuộc tính được tạo ra sẽ được điền dữ liệu bằng cách đọc thuộc tính từ đối tượng giám sát.
template<typename I,typename D,typename S>
class MonitorInterface
{
...
class TradeState
{
public:
...
long ulongs[];
double doubles[];
string strings[];
const MonitorInterface *owner;
TradeState(const MonitorInterface *ptr) : owner(ptr)
{
...
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Toàn bộ lớp TradeState
đã được công khai vì các trường của nó cần được truy cập từ đối tượng giám sát cha (được truyền dưới dạng con trỏ đến hàm khởi tạo), và ngoài ra TradeState
sẽ chỉ được sử dụng trong phần bảo vệ của giám sát (chúng không thể truy cập từ bên ngoài).
Để điền ba mảng với giá trị thuộc tính của ba loại khác nhau, trước tiên bạn phải tìm ra sự phân bố của các thuộc tính theo loại và chỉ số trong mỗi mảng cụ thể.
Đối với mỗi loại đối tượng giao dịch (lệnh, giao dịch và vị thế), các định danh của 3 liệt kê tương ứng với các thuộc tính của các loại khác nhau không giao nhau và tạo thành một hệ thống đánh số liên tục. Hãy chứng minh điều này.
Trong chương Liệt kê, chúng ta đã xem xét script ConversionEnum.mq5
triển khai hàm process
để ghi lại tất cả các phần tử của một liệt kê cụ thể. Script đó đã kiểm tra liệt kê ENUM_APPLIED_PRICE
. Bây giờ chúng ta có thể tạo một bản sao của script và phân tích ba liệt kê khác. Ví dụ, như sau:
void OnStart()
{
process((ENUM_POSITION_PROPERTY_INTEGER)0);
process((ENUM_POSITION_PROPERTY_DOUBLE)0);
process((ENUM_POSITION_PROPERTY_STRING)0);
}
2
3
4
5
6
Kết quả của việc thực thi, chúng ta nhận được nhật ký sau. Cột bên trái chứa số thứ tự bên trong các liệt kê, và các giá trị bên phải (sau dấu '=') là các hằng số tích hợp (định danh) của các phần tử.
ENUM_POSITION_PROPERTY_INTEGER Count=9
0 POSITION_TIME=1
1 POSITION_TYPE=2
2 POSITION_MAGIC=12
3 POSITION_IDENTIFIER=13
4 POSITION_TIME_MSC=14
5 POSITION_TIME_UPDATE=15
6 POSITION_TIME_UPDATE_MSC=16
7 POSITION_TICKET=17
8 POSITION_REASON=18
ENUM_POSITION_PROPERTY_DOUBLE Count=8
0 POSITION_VOLUME=3
1 POSITION_PRICE_OPEN=4
2 POSITION_PRICE_CURRENT=5
3 POSITION_SL=6
4 POSITION_TP=7
5 POSITION_COMMISSION=8
6 POSITION_SWAP=9
7 POSITION_PROFIT=10
ENUM_POSITION_PROPERTY_STRING Count=3
0 POSITION_SYMBOL=0
1 POSITION_COMMENT=11
2 POSITION_EXTERNAL_ID=19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Ví dụ, thuộc tính với hằng số 0 là chuỗi POSITION_SYMBOL
, các thuộc tính với hằng số 1 và 2 là số nguyên POSITION_TIME
và POSITION_TYPE
, thuộc tính với hằng số 3 là số thực POSITION_VOLUME
, v.v.
Do đó, các hằng số là một hệ thống chỉ số xuyên suốt trên các thuộc tính của tất cả các loại, và chúng ta có thể sử dụng cùng một thuật toán (dựa trên EnumToArray.mqh
) để lấy chúng.
Đối với mỗi thuộc tính, bạn cần ghi nhớ loại của nó (xác định mảng nào trong ba mảng sẽ lưu trữ giá trị) và số thứ tự trong số các thuộc tính cùng loại (đây sẽ là chỉ số của phần tử trong mảng tương ứng). Ví dụ, chúng ta thấy rằng các vị thế chỉ có 3 thuộc tính chuỗi, vì vậy mảng strings
trong ảnh chụp của một vị thế sẽ phải có cùng kích thước, và POSITION_SYMBOL
(0), POSITION_COMMENT
(11), và POSITION_EXTERNAL_ID
(19) sẽ được ghi vào các chỉ số 0, 1 và 2 của nó.
Việc chuyển đổi các chỉ số xuyên suốt của thuộc tính thành loại của chúng (một trong PROP_TYPE
) và thành số thứ tự trong mảng của loại tương ứng có thể được thực hiện một lần khi bắt đầu chương trình vì các liệt kê với thuộc tính là hằng số (được tích hợp vào hệ thống). Chúng ta ghi bảng định địa chỉ gián tiếp kết quả vào mảng hai chiều tĩnh indices
. Kích thước của nó trong chiều đầu tiên sẽ được xác định động là tổng số thuộc tính (của cả 3 loại). Chúng ta sẽ ghi kích thước vào biến tĩnh limit
. Một vài ô được phân bổ cho chiều thứ hai: indices[i][0]
— loại PROP_TYPE
, indices[i][1]
— chỉ số trong một trong các mảng ulongs
, doubles
, hoặc strings
(tùy thuộc vào indices[i][0]
).
class TradeState
{
...
static int indices[][2];
static int j, d, s;
public:
const static int limit;
static PROP_TYPE type(const int i)
{
return (PROP_TYPE)indices[i][0];
}
static int offset(const int i)
{
return indices[i][1];
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Các biến j
, d
, và s
sẽ được sử dụng để lập chỉ số tuần tự các thuộc tính trong mỗi loại khác nhau. Đây là cách nó được thực hiện trong phương thức tĩnh calcIndices
.
static int calcIndices()
{
const int size = fmax(boundary<I>(),
fmax(boundary<D>(), boundary<S>())) + 1;
ArrayResize(indices, size);
j = d = s = 0;
for(int i = 0; i < size; ++i)
{
if(detect<I>(i))
{
indices[i][0] = PROP_TYPE_INTEGER;
indices[i][1] = j++;
}
else if(detect<D>(i))
{
indices[i][0] = PROP_TYPE_DOUBLE;
indices[i][1] = d++;
}
else if(detect<S>(i))
{
indices[i][0] = PROP_TYPE_STRING;
indices[i][1] = s++;
}
else
{
Print("Unresolved int value as enum: ", i, " ", typename(TradeState));
}
}
return size;
}
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
Phương thức boundary
trả về hằng số tối đa trong tất cả các phần tử của liệt kê E đã cho.
template<typename E>
static int boundary(const E dummy = (E)NULL)
{
int values[];
const int n = EnumToArray(dummy, values, 0, 1000);
ArraySort(values);
return values[n - 1];
}
2
3
4
5
6
7
8
Giá trị lớn nhất của tất cả ba loại liệt kê xác định phạm vi số nguyên cần được sắp xếp theo loại thuộc tính mà chúng thuộc về.
Ở đây chúng ta sử dụng phương thức detect
trả về true
nếu số nguyên là một phần tử của một liệt kê.
template<typename E>
static bool detect(const int v)
{
ResetLastError();
const string s = EnumToString((E)v); // kết quả không được sử dụng
if(_LastError == 0) // chỉ sự vắng mặt của lỗi là quan trọng
{
return true;
}
return false;
}
2
3
4
5
6
7
8
9
10
11
Câu hỏi cuối cùng là làm thế nào để chạy phép tính này khi chương trình bắt đầu. Điều này được thực hiện bằng cách tận dụng tính chất tĩnh của các biến và phương thức.
template<typename I,typename D,typename S>
static int MonitorInterface::TradeState::indices[][2];
template<typename I,typename D,typename S>
static int MonitorInterface::TradeState::j,
MonitorInterface::TradeState::d,
MonitorInterface::TradeState::s;
template<typename I,typename D,typename S>
const static int MonitorInterface::TradeState::limit =
MonitorInterface::TradeState::calcIndices();
2
3
4
5
6
7
8
9
Lưu ý rằng limit
được khởi tạo bởi kết quả của việc gọi hàm calcIndices
của chúng ta.
Có bảng với các chỉ số, chúng ta thực hiện việc điền các mảng với giá trị thuộc tính trong phương thức cache
.
class TradeState
{
...
TradeState(const MonitorInterface *ptr) : owner(ptr)
{
cache(); // khi tạo đối tượng, lập tức lưu trữ các thuộc tính
}
template<typename T>
void _get(const int e, T &value) const // quá tải với bản ghi bằng tham chiếu
{
value = owner.get(e, value);
}
void cache()
{
ArrayResize(ulongs, j);
ArrayResize(doubles, d);
ArrayResize(strings, s);
for(int i = 0; i < limit; ++i)
{
switch(indices[i][0])
{
case PROP_TYPE_INTEGER: _get(i, ulongs[indices[i][1]]); break;
case PROP_TYPE_DOUBLE: _get(i, doubles[indices[i][1]]); break;
case PROP_TYPE_STRING: _get(i, strings[indices[i][1]]); 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
Chúng ta lặp qua toàn bộ phạm vi thuộc tính từ 0 đến limit
và, tùy thuộc vào loại thuộc tính trong indices[i][0]
, ghi giá trị của nó vào phần tử của mảng ulongs
, doubles
, hoặc strings
dưới số indices[i][1]
(phần tử tương ứng của mảng được truyền bằng tham chiếu đến phương thức _get
).
Một lời gọi của owner.get(e, value)
đề cập đến một trong những phương thức tiêu chuẩn của lớp giám sát (ở đây nó hiển thị dưới dạng con trỏ trừu tượng MonitorInterface
). Cụ thể, đối với các vị thế trong lớp PositionMonitor
, điều này sẽ dẫn đến các lời gọi PositionGetInteger
, PositionGetDouble
, hoặc PositionGetString
. Trình biên dịch sẽ chọn loại đúng. Các giám sát lệnh và giao dịch có triển khai tương tự riêng, được tự động bao gồm bởi mã cơ sở này.
Việc kế thừa mô tả của một ảnh chụp của một đối tượng giao dịch từ lớp giám sát là hợp lý. Vì chúng ta phải lưu trữ lệnh, giao dịch và vị thế, nên việc tạo lớp mới dưới dạng mẫu và tập hợp tất cả các thuật toán chung phù hợp với tất cả các đối tượng trong đó là hợp lý. Hãy gọi nó là TradeBaseState
(tệp TradeState.mqh
).
template<typename M,typename I,typename D,typename S>
class TradeBaseState: public M
{
M::TradeState state;
bool cached;
public:
TradeBaseState(const ulong t) : M(t), state(&this), cached(ready)
{
}
void passthrough(const bool b) // bật/tắt bộ nhớ cache theo ý muốn
{
cached = b;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Một trong những lớp giám sát cụ thể được mô tả trước đó được ẩn dưới chữ cái M (OrderMonitor.mqh
, PositionMonitor.mqh
, DealMonitor.mqh
). Nền tảng là đối tượng lưu trữ state
của lớp M::TradeState
vừa được giới thiệu. Tùy thuộc vào M, một bảng chỉ số cụ thể sẽ được hình thành bên trong (một cho lớp M) và các mảng thuộc tính sẽ được phân phối (riêng cho mỗi thể hiện của M, tức là cho mỗi lệnh, giao dịch, vị thế).
Biến cached
chứa dấu hiệu liệu các mảng trong state
có được điền giá trị thuộc tính hay không, và liệu có nên truy vấn thuộc tính trên đối tượng để trả về giá trị từ bộ nhớ cache hay không. Điều này sẽ cần thiết sau này để so sánh trạng thái đã lưu và trạng thái hiện tại.
Nói cách khác, khi cached
được đặt thành false
, đối tượng sẽ hoạt động như một giám sát thông thường, đọc thuộc tính từ môi trường giao dịch. Khi cached
bằng true
, đối tượng sẽ trả về các giá trị đã lưu trước đó từ các mảng nội bộ.
virtual long get(const I property) const override
{
return cached ? state.ulongs[M::TradeState::offset(property)] : M::get(property);
}
virtual double get(const D property) const override
{
return cached ? state.doubles[M::TradeState::offset(property)] : M::get(property);
}
virtual string get(const S property) const override
{
return cached ? state.strings[M::TradeState::offset(property)] : M::get(property);
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Theo mặc định, việc lưu trữ tất nhiên là được bật.
Chúng ta cũng phải cung cấp một phương thức trực tiếp thực hiện việc lưu trữ (điền vào các mảng). Để làm điều này, chỉ cần gọi phương thức cache
cho đối tượng state
.
bool update()
{
if(refresh())
{
cached = false; // tắt đọc từ bộ nhớ cache
state.cache(); // đọc thuộc tính thực và ghi vào bộ nhớ cache
cached = true; // bật lại quyền truy cập bộ nhớ cache bên ngoài
return true;
}
return false;
}
2
3
4
5
6
7
8
9
10
11
Phương thức refresh
là gì?
Cho đến nay, chúng ta đã sử dụng các đối tượng giám sát ở chế độ đơn giản: tạo, đọc thuộc tính và xóa chúng. Đồng thời, việc đọc thuộc tính假 định rằng lệnh, giao dịch hoặc vị thế tương ứng đã được chọn trong bối cảnh giao dịch (bên trong hàm khởi tạo). Vì chúng ta hiện đang cải tiến các giám sát để hỗ trợ trạng thái nội bộ, cần đảm bảo rằng phần tử mong muốn được phân bổ lại để đọc các thuộc tính ngay cả sau một khoảng thời gian không xác định (tất nhiên, với việc kiểm tra rằng phần tử vẫn tồn tại). Để thực hiện điều này, chúng ta đã thêm phương thức ảo refresh
vào lớp mẫu MonitorInterface
.
// TradeBaseMonitor.mqh
template<typename I,typename D,typename S>
class MonitorInterface
{
...
virtual bool refresh() = 0;
2
3
4
5
6
Nó phải trả về true
khi phân bổ thành công một lệnh, giao dịch hoặc vị thế. Nếu kết quả là false
, một trong những lỗi sau nên được chứa trong biến tích hợp _LastError
:
4753 ERR_TRADE_POSITION_NOT_FOUND;
4754 ERR_TRADE_ORDER_NOT_FOUND;
4755 ERR_TRADE_DEAL_NOT_FOUND;
Trong trường hợp này, biến thành viên ready
, báo hiệu sự sẵn sàng của đối tượng, phải được đặt lại thành false
trong các triển khai của phương thức này trong các lớp dẫn xuất.
Ví dụ, trong hàm khởi tạo PositionMonitor
, chúng ta đã và vẫn có một khởi tạo như vậy. Tình huống tương tự cũng áp dụng cho các giám sát lệnh và giao dịch.
// PositionMonitor.mqh
const ulong ticket;
PositionMonitor(const ulong t): ticket(t)
{
if(!PositionSelectByTicket(ticket))
{
PrintFormat("Error: PositionSelectByTicket(%lld) failed: %s", ticket,
E2S(_LastError));
}
else
{
ready = true;
}
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bây giờ chúng ta sẽ thêm phương thức refresh
vào tất cả các lớp cụ thể thuộc loại này (xem ví dụ PositionMonitor
):
// PositionMonitor.mqh
virtual bool refresh() override
{
ready = PositionSelectByTicket(ticket);
return ready;
}
2
3
4
5
6
Nhưng việc điền các mảng bộ nhớ cache với giá trị thuộc tính chỉ là một nửa công việc. Nửa còn lại là so sánh các giá trị này với trạng thái thực tế của lệnh, giao dịch hoặc vị thế.
Để xác định sự khác biệt và ghi các chỉ số của các thuộc tính đã thay đổi vào mảng changes
, lớp được tạo ra TradeBaseState
cung cấp phương thức getChanges
. Phương thức này trả về true
khi phát hiện có thay đổi.
template<typename M,typename I,typename D,typename S>
class TradeBaseState: public M
{
...
bool getChanges(int &changes[])
{
const bool previous = ready;
if(refresh())
{
// phần tử được chọn trong môi trường giao dịch = có thể đọc và so sánh thuộc tính
cached = false; // đọc trực tiếp
const bool result = M::diff(state, changes);
cached = true; // bật lại bộ nhớ cache theo mặc định
return result;
}
// không còn "ready" = có khả năng đã bị xóa
return previous != ready; // nếu vừa bị xóa, đây cũng là một thay đổi
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Như bạn thấy, công việc chính được giao cho một phương thức diff
nào đó trong lớp M. Đây là một phương thức mới: chúng ta cần viết nó. May mắn thay, nhờ OOP, bạn có thể làm điều này một lần trong mẫu cơ sở MonitorInterface
, và phương thức sẽ xuất hiện ngay lập tức cho lệnh, giao dịch và vị thế.
// TradeBaseMonitor.mqh
template<typename I,typename D,typename S>
class MonitorInterface
{
...
bool diff(const TradeState &that, int &changes[])
{
ArrayResize(changes, 0);
for(int i = 0; i < TradeState::limit; ++i)
{
switch(TradeState::indices[i][0])
{
case PROP_TYPE_INTEGER:
if(this.get((I)i) != that.ulongs[TradeState::offset(i)])
{
PUSH(changes, i);
}
break;
case PROP_TYPE_DOUBLE:
if(!TU::Equal(this.get((D)i), that.doubles[TradeState::offset(i)]))
{
PUSH(changes, i);
}
break;
case PROP_TYPE_STRING:
if(this.get((S)i) != that.strings[TradeState::offset(i)])
{
PUSH(changes, i);
}
break;
}
}
return ArraySize(changes) > 0;
}
}
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ậy là mọi thứ đã sẵn sàng để tạo các lớp lưu trữ cụ thể cho lệnh, giao dịch và vị thế. Ví dụ, các vị thế sẽ được lưu trữ trong giám sát mở rộng PositionState
dựa trên PositionMonitor
.
class PositionState: public TradeBaseState<PositionMonitor,
ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,
ENUM_POSITION_PROPERTY_STRING>
{
public:
PositionState(const long t): TradeBaseState(t) { }
};
2
3
4
5
6
7
8
Tương tự, một lớp lưu trữ cho giao dịch được định nghĩa trong tệp TradeState.mqh
.
class DealState: public TradeBaseState<DealMonitor,
ENUM_DEAL_PROPERTY_INTEGER,
ENUM_DEAL_PROPERTY_DOUBLE,
ENUM_DEAL_PROPERTY_STRING>
{
public:
DealState(const long t): TradeBaseState(t) { }
};
2
3
4
5
6
7
8
Với lệnh, mọi thứ phức tạp hơn một chút, vì chúng có thể là lệnh đang hoạt động và lệnh lịch sử. Cho đến nay, chúng ta đã có một lớp giám sát chung cho lệnh, OrderMonitor
. Nó cố gắng tìm ticket của lệnh được gửi cả trong số các lệnh đang hoạt động và trong lịch sử. Cách tiếp cận này không phù hợp cho việc lưu trữ, vì các Expert Advisor cần theo dõi sự chuyển đổi của lệnh từ trạng thái này sang trạng thái khác.
Vì lý do này, chúng ta thêm 2 lớp cụ thể hơn vào tệp OrderMonitor.mqh
: ActiveOrderMonitor
và HistoryOrderMonitor
.
// OrderMonitor.mqh
class ActiveOrderMonitor: public OrderMonitor
{
public:
ActiveOrderMonitor(const ulong t): OrderMonitor(t)
{
if(history) // nếu lệnh nằm trong lịch sử, thì nó đã không còn hoạt động
{
ready = false; // đặt lại cờ ready
history = false; // đối tượng này chỉ dành cho lệnh đang hoạt động theo định nghĩa
}
}
virtual bool refresh() override
{
ready = OrderSelect(ticket);
return ready;
}
};
class HistoryOrderMonitor: public OrderMonitor
{
public:
HistoryOrderMonitor(const ulong t): OrderMonitor(t) { }
virtual bool refresh() override
{
history = true; // chỉ làm việc với lịch sử
ready = historyOrderSelectWeak(ticket);
return ready; // sự sẵn sàng được xác định bởi sự hiện diện của ticket trong lịch sử
}
};
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
Mỗi lớp trong số chúng chỉ tìm kiếm ticket trong khu vực của mình. Dựa trên các giám sát này, bạn đã có thể tạo các lớp lưu trữ.
// TradeState.mqh
class OrderState: public TradeBaseState<ActiveOrderMonitor,
ENUM_ORDER_PROPERTY_INTEGER,
ENUM_ORDER_PROPERTY_DOUBLE,
ENUM_ORDER_PROPERTY_STRING>
{
public:
OrderState(const long t): TradeBaseState(t) { }
};
class HistoryOrderState: public TradeBaseState<HistoryOrderMonitor,
ENUM_ORDER_PROPERTY_INTEGER,
ENUM_ORDER_PROPERTY_DOUBLE,
ENUM_ORDER_PROPERTY_STRING>
{
public:
HistoryOrderState(const long t): TradeBaseState(t) { }
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Điểm nhấn cuối cùng mà chúng ta sẽ thêm vào lớp TradeBaseState
để thuận tiện là một phương thức đặc biệt để chuyển đổi giá trị thuộc tính thành chuỗi. Mặc dù có một số phiên bản của phương thức stringify
trong giám sát, tất cả chúng sẽ "in" hoặc giá trị từ bộ nhớ cache (nếu biến thành viên cached
bằng true
) hoặc giá trị từ đối tượng gốc của môi trường giao dịch (nếu cached
bằng false
). Để hiển thị sự khác biệt giữa bộ nhớ cache và đối tượng đã thay đổi (khi những khác biệt này được tìm thấy), chúng ta cần đồng thời đọc giá trị từ bộ nhớ cache và bỏ qua bộ nhớ cache. Về vấn đề này, chúng ta thêm phương thức stringifyRaw
luôn làm việc trực tiếp với thuộc tính (do biến cached
được tạm thời đặt lại và cài đặt lại).
// lấy biểu diễn chuỗi của thuộc tính 'i' mà không qua bộ nhớ cache
string stringifyRaw(const int i)
{
const bool previous = cached;
cached = false;
const string s = stringify(i);
cached = previous;
}
2
3
4
5
6
7
8
Hãy kiểm tra hiệu suất của giám sát lưu trữ bằng một ví dụ đơn giản về Expert Advisor giám sát trạng thái của một lệnh đang hoạt động (OrderSnapshot.mq5
). Sau này, chúng ta sẽ phát triển ý tưởng này để lưu trữ bất kỳ tập hợp lệnh, giao dịch hoặc vị thế nào, tức là tạo ra một bộ nhớ cache hoàn chỉnh.
Expert Advisor sẽ cố gắng tìm lệnh cuối cùng trong danh sách các lệnh đang hoạt động và tạo đối tượng OrderState
cho nó. Nếu không có lệnh nào, người dùng sẽ được nhắc tạo một lệnh hoặc mở một vị thế (việc sau liên quan đến việc đặt và thực thi một lệnh trên thị trường). Ngay khi tìm thấy một lệnh, chúng ta kiểm tra xem trạng thái lệnh có thay đổi không. Việc kiểm tra này được thực hiện trong trình xử lý OnTrade
. Expert Advisor sẽ tiếp tục giám sát lệnh này cho đến khi nó bị gỡ bỏ.
int OnInit()
{
if(OrdersTotal() == 0)
{
Alert("Please, create a pending order or open/close a position");
}
else
{
OnTrade(); // tự gọi
}
return INIT_SUCCEEDED;
}
void OnTrade()
{
static int count = 0;
// con trỏ đối tượng được lưu trữ trong AutoPtr tĩnh
static AutoPtr<OrderState> auto;
// lấy con trỏ "sạch" (để không phải giải tham chiếu auto[] ở khắp mọi nơi)
OrderState *state = auto[];
PrintFormat(">> OnTrade(%d)", count++);
if(OrdersTotal() > 0 && state == NULL)
{
const ulong ticket = OrderGetTicket(OrdersTotal() - 1);
auto = new OrderState(ticket);
PrintFormat("Order picked up: %lld %s", ticket,
auto[].isReady() ? "true" : "false");
auto[].print(); // trạng thái ban đầu tại thời điểm "bắt" lệnh
}
else if(state)
{
int changes[];
if(state.getChanges(changes))
{
Print("Order properties changed:");
ArrayPrint(changes);
...
}
if(_LastError != 0) Print(E2S(_LastError));
}
}
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
Ngoài việc hiển thị mảng các thuộc tính đã thay đổi, sẽ rất tốt nếu hiển thị chính những thay đổi đó. Do đó, thay vì dấu chấm lửng, chúng ta sẽ thêm đoạn mã sau (nó sẽ hữu ích cho chúng ta trong các lớp bộ nhớ cache hoàn chỉnh trong tương lai).
for(int k = 0; k < ArraySize(changes); ++k)
{
switch(OrderState::TradeState::type(changes[k]))
{
case PROP_TYPE_INTEGER:
Print(EnumToString((ENUM_ORDER_PROPERTY_INTEGER)changes[k]), ": ",
state.stringify(changes[k]), " -> ",
state.stringifyRaw(changes[k]));
break;
case PROP_TYPE_DOUBLE:
Print(EnumToString((ENUM_ORDER_PROPERTY_DOUBLE)changes[k]), ": ",
state.stringify(changes[k]), " -> ",
state.stringifyRaw(changes[k]));
break;
case PROP_TYPE_STRING:
Print(EnumToString((ENUM_ORDER_PROPERTY_STRING)changes[k]), ": ",
state.stringify(changes[k]), " -> ",
state.stringifyRaw(changes[k]));
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Ở đây chúng ta sử dụng phương thức mới stringifyRaw
. Sau khi hiển thị các thay đổi, đừng quên cập nhật trạng thái bộ nhớ cache.
state.update();
Nếu bạn chạy Expert Advisor trên một tài khoản không có lệnh đang hoạt động và đặt một lệnh mới, bạn sẽ thấy các mục sau trong nhật ký (ở đây buy limit
cho EURUSD được tạo dưới giá thị trường hiện tại).
Alert: Please, create a pending order or open/close a position
>>> OnTrade(0)
Order picked up: 1311736135 true
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
0 ORDER_TIME_SETUP=2022.04.11 11:42:39
1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
2 ORDER_TIME_DONE=1970.01.01 00:00:00
3 ORDER_TYPE=ORDER_TYPE_BUY_LIMIT
4 ORDER_TYPE_FILLING=ORDER_FILLING_RETURN
5 ORDER_TYPE_TIME=ORDER_TIME_GTC
6 ORDER_STATE=ORDER_STATE_STARTED
7 ORDER_MAGIC=0
8 ORDER_POSITION_ID=0
9 ORDER_TIME_SETUP_MSC=2022.04.11 11:42:39'729
10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000
11 ORDER_POSITION_BY_ID=0
12 ORDER_TICKET=1311736135
13 ORDER_REASON=ORDER_REASON_CLIENT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
0 ORDER_VOLUME_INITIAL=0.01
1 ORDER_VOLUME_CURRENT=0.01
2 ORDER_PRICE_OPEN=1.087
3 ORDER_PRICE_CURRENT=1.087
4 ORDER_PRICE_STOPLIMIT=0.0
5 ORDER_SL=0.0
6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
0 ORDER_SYMBOL=EURUSD
1 ORDER_COMMENT=
2 ORDER_EXTERNAL_ID=
>>> OnTrade(1)
Order properties changed:
10 14
ORDER_PRICE_CURRENT: 1.087 -> 1.09073
ORDER_STATE: ORDER_STATE_STARTED -> ORDER_STATE_PLACED
>>> OnTrade(2)
>>> OnTrade(3)
>>> OnTrade(4)
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
Ở đây bạn có thể thấy trạng thái của lệnh đã thay đổi từ STARTED sang PLACED như thế nào. Nếu thay vì một lệnh chờ, chúng ta mở trên thị trường với khối lượng nhỏ, chúng ta có thể không kịp nhận được những thay đổi này, vì những lệnh như vậy thường được thiết lập rất nhanh, và trạng thái quan sát được của chúng thay đổi từ STARTED ngay lập tức sang FILLED. Và trạng thái sau đã có nghĩa là lệnh đã được chuyển vào lịch sử. Do đó, cần theo dõi lịch sử song song để theo dõi chúng. Chúng ta sẽ thể hiện điều này trong ví dụ tiếp theo.
Lưu ý rằng có thể có nhiều sự kiện OnTrade
nhưng không phải tất cả đều liên quan đến lệnh của chúng ta.
Hãy thử đặt mức Take Profit
và kiểm tra nhật ký.
>>> OnTrade(5)
Order properties changed:
10 13
ORDER_PRICE_CURRENT: 1.09073 -> 1.09079
ORDER_TP: 0.0 -> 1.097
>>> OnTrade(6)
>>> OnTrade(7)
2
3
4
5
6
7
Tiếp theo, thay đổi ngày hết hạn: từ GTC sang một ngày.
>>> OnTrade(8)
Order properties changed:
10
ORDER_PRICE_CURRENT: 1.09079 -> 1.09082
>>> OnTrade(9)
>>> OnTrade(10)
Order properties changed:
2 6
ORDER_TIME_EXPIRATION: 1970.01.01 00:00:00 -> 2022.04.11 00:00:00
ORDER_TYPE_TIME: ORDER_TIME_GTC -> ORDER_TIME_DAY
>>> OnTrade(11)
2
3
4
5
6
7
8
9
10
11
Ở đây, trong quá trình thay đổi lệnh của chúng ta, giá đã có đủ thời gian để thay đổi, và do đó chúng ta "bắt" được một thông báo trung gian về giá trị mới trong ORDER_PRICE_CURRENT. Và chỉ sau đó, những thay đổi mong đợi trong ORDER_TYPE_TIME và ORDER_TIME_EXPIRATION mới được ghi vào nhật ký.
Tiếp theo, chúng ta đã xóa lệnh.
>>> OnTrade(12)
TRADE_ORDER_NOT_FOUND
2
Bây giờ, đối với bất kỳ hành động nào với tài khoản dẫn đến các sự kiện OnTrade
, Expert Advisor của chúng ta sẽ xuất ra TRADE_ORDER_NOT_FOUND, vì nó được thiết kế để theo dõi một lệnh duy nhất. Nếu Expert Advisor được khởi động lại, nó sẽ "bắt" một lệnh khác nếu có. Nhưng chúng ta sẽ dừng Expert Advisor và bắt đầu chuẩn bị cho một nhiệm vụ cấp bách hơn.
Thông thường, việc lưu trữ và kiểm soát thay đổi không chỉ cần thiết cho một lệnh hoặc vị thế duy nhất, mà cho tất cả hoặc một tập hợp của chúng, được chọn theo các điều kiện nhất định. Để phục vụ các mục đích này, chúng ta sẽ phát triển một lớp mẫu cơ sở TradeCache
(TradeCache.mqh
) và dựa trên đó, chúng ta sẽ tạo các lớp ứng dụng cho danh sách lệnh, giao dịch và vị thế.
template<typename T,typename F,typename E>
class TradeCache
{
AutoPtr<T> data[];
const E property;
const int NOT_FOUND_ERROR;
public:
TradeCache(const E id, const int error): property(id), NOT_FOUND_ERROR(error) { }
virtual string rtti() const
{
return typename(this); // sẽ được định nghĩa lại trong các lớp dẫn xuất cho đầu ra trực quan vào nhật ký
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Trong mẫu này, chữ cái T biểu thị một trong những lớp của gia đình TradeState
. Như bạn thấy, một mảng các đối tượng như vậy dưới dạng con trỏ tự động được dành sẵn dưới tên data
.
Chữ cái F mô tả kiểu của một trong các lớp bộ lọc (OrderFilter.mqh
, bao gồm HistoryOrderFilter
, DealFilter.mqh
, PositionFilter.mqh
) được sử dụng để chọn các mục được lưu trữ. Trong trường hợp đơn giản nhất, khi bộ lọc không chứa điều kiện let
, tất cả các phần tử sẽ được lưu trữ (liên quan đến lịch sử lấy mẫu cho các đối tượng từ lịch sử).
Chữ cái E tương ứng với kiểu liệt kê trong đó thuộc tính property
xác định các đối tượng được đặt. Vì thuộc tính này thường là SOME_TICKET, kiểu liệt kê được giả định là một kiểu số nguyên ENUM_SOMETHING_PROPERTY_INTEGER.
Biến NOT_FOUND_ERROR
được thiết kế cho mã lỗi xảy ra khi cố gắng phân bổ một đối tượng không tồn tại để đọc, ví dụ, ERR_TRADE_POSITION_NOT_FOUND
cho các vị thế.
Trong các tham số, phương thức chính của lớp scan
nhận một tham chiếu đến bộ lọc đã được cấu hình (nó nên được cấu hình bởi mã gọi).
void scan(F &f)
{
const int existedBefore = ArraySize(data);
ulong tickets[];
ArrayResize(tickets, existedBefore);
for(int i = 0; i < existedBefore; ++i)
{
tickets[i] = data[i][].get(property);
}
...
2
3
4
5
6
7
8
9
10
11
Ở đầu phương thức, chúng ta thu thập các định danh của các đối tượng đã được lưu trữ vào mảng tickets
. Rõ ràng, trong lần chạy đầu tiên, nó sẽ trống.
Tiếp theo, chúng ta điền mảng objects
với các vé của các đối tượng liên quan bằng bộ lọc. Đối với mỗi vé mới, chúng ta tạo một đối tượng giám sát lưu trữ T và thêm nó vào mảng data
. Đối với các đối tượng cũ, chúng ta phân tích sự hiện diện của các thay đổi bằng cách gọi data[j][].getChanges(changes)
và sau đó cập nhật bộ nhớ cache bằng cách gọi data[j][].update()
.
ulong objects[];
f.select(objects);
for(int i = 0, j; i < ArraySize(objects); ++i)
{
const ulong ticket = objects[i];
for(j = 0; j < existedBefore; ++j)
{
if(tickets[j] == ticket)
{
tickets[j] = 0; // đánh dấu là đã tìm thấy
break;
}
}
if(j == existedBefore) // không có trong bộ nhớ cache, cần thêm
{
const T *ptr = new T(ticket);
PUSH(data, ptr);
onAdded(*ptr);
}
else
{
ResetLastError();
int changes[];
if(data[j][].getChanges(changes))
{
onUpdated(data[j][], changes);
data[j][].update();
}
if(_LastError) PrintFormat("%s: %lld (%s)", rtti(), ticket, E2S(_LastError));
}
}
...
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
Như bạn thấy, trong mỗi giai đoạn thay đổi, tức là khi một đối tượng được thêm vào hoặc sau khi nó thay đổi, các phương thức onAdded
và onUpdated
được gọi. Đây là các phương thức giả lập ảo mà scan
có thể sử dụng để thông báo cho chương trình về các sự kiện tương ứng. Mã ứng dụng được kỳ vọng sẽ triển khai một lớp dẫn xuất với các phiên bản ghi đè của các phương thức này. Chúng ta sẽ đề cập đến vấn đề này sau, nhưng hiện tại, chúng ta sẽ tiếp tục xem xét phương thức scan
.
Trong vòng lặp trên, tất cả các vé được tìm thấy trong mảng tickets
được đặt thành số không, và do đó các phần tử còn lại tương ứng với các đối tượng bị thiếu trong môi trường giao dịch. Tiếp theo, chúng được kiểm tra bằng cách gọi getChanges
và so sánh mã lỗi với NOT_FOUND_ERROR
. Nếu điều này đúng, phương thức ảo onRemoved
được gọi. Nó trả về một cờ boolean (do mã ứng dụng của bạn cung cấp) cho biết liệu mục có nên bị xóa khỏi bộ nhớ cache hay không.
for(int j = 0; j < existedBefore; ++j)
{
if(tickets[j] == 0) continue; // bỏ qua các phần tử đã xử lý
// vé này không được tìm thấy, có thể đã bị xóa
int changes[];
ResetLastError();
if(data[j][].getChanges(changes))
{
if(_LastError == NOT_FOUND_ERROR) // ví dụ, ERR_TRADE_POSITION_NOT_FOUND
{
if(onRemoved(data[j][]))
{
data[j] = NULL; // giải phóng đối tượng và phần tử mảng
}
continue;
}
// NB! Thông thường chúng ta không nên rơi vào đây
PrintFormat("Unexpected ticket: %lld (%s) %s", tickets[j],
E2S(_LastError), rtti());
onUpdated(data[j][], changes, true);
data[j][].update();
}
else
{
PrintFormat("Orphaned element: %lld (%s) %s", tickets[j],
E2S(_LastError), rtti());
}
}
}
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
Vào cuối phương thức scan
, mảng data
được dọn sạch các phần tử null nhưng đoạn này được bỏ qua ở đây để ngắn gọn.
Lớp cơ sở cung cấp các triển khai tiêu chuẩn của các phương thức onAdded
, onRemoved
, và onUpdated
hiển thị bản chất của các sự kiện trong nhật ký. Bằng cách định nghĩa macro PRINT_DETAILS
trong mã của bạn trước khi bao gồm tệp tiêu đề TradeCache.mqh
, bạn có thể yêu cầu in ra tất cả các thuộc tính của mỗi đối tượng mới.
virtual void onAdded(const T &state)
{
Print(rtti(), " added: ", state.get(property));
#ifdef PRINT_DETAILS
state.print();
#endif
}
virtual bool onRemoved(const T &state)
{
Print(rtti(), " removed: ", state.get(property));
return true; // cho phép đối tượng bị xóa khỏi bộ nhớ cache (false để giữ lại)
}
virtual void onUpdated(T &state, const int &changes[],
const bool unexpected = false)
{
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Chúng ta sẽ không trình bày phương thức onUpdated
, vì nó gần như lặp lại mã để xuất các thay đổi từ Expert Advisor OrderSnapshot.mq5
đã được hiển thị ở trên.
Tất nhiên, lớp cơ sở có các tiện ích để lấy kích thước của bộ nhớ cache và truy cập một đối tượng cụ thể theo số.
int size() const
{
return ArraySize(data);
}
T *operator[](int i) const
{
return data[i][]; // trả về con trỏ (T*) từ đối tượng AutoPtr
}
2
3
4
5
6
7
8
9
Ngay sau khi khởi chạy Expert Advisor trên một tài khoản sạch, chúng ta sẽ thấy thông báo sau:
>>> OnTrade(0)
>>> positions: 0, orders: 0, history: 0
2
Hãy thử thực hiện trường hợp kiểm tra đơn giản nhất: hãy mua hoặc bán trên một tài khoản "trống" không có vị thế mở và lệnh chờ. Nhật ký sẽ ghi lại các sự kiện sau (xảy ra gần như ngay lập tức).
Đầu tiên, một lệnh đang hoạt động sẽ được phát hiện.
>>> OnTrade(1)
OrderCache added: 1311792104
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
0 ORDER_TIME_SETUP=2022.04.11 12:34:51
1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
2 ORDER_TIME_DONE=1970.01.01 00:00:00
3 ORDER_TYPE=ORDER_TYPE_BUY
4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
5 ORDER_TYPE_TIME=ORDER_TIME_GTC
6 ORDER_STATE=ORDER_STATE_STARTED
7 ORDER_MAGIC=0
8 ORDER_POSITION_ID=0
9 ORDER_TIME_SETUP_MSC=2022.04.11 12:34:51'096
10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000
11 ORDER_POSITION_BY_ID=0
12 ORDER_TICKET=1311792104
13 ORDER_REASON=ORDER_REASON_CLIENT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
0 ORDER_VOLUME_INITIAL=0.01
1 ORDER_VOLUME_CURRENT=0.01
2 ORDER_PRICE_OPEN=1.09218
3 ORDER_PRICE_CURRENT=1.09218
4 ORDER_PRICE_STOPLIMIT=0.0
5 ORDER_SL=0.0
6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
0 ORDER_SYMBOL=EURUSD
1 ORDER_COMMENT=
2 ORDER_EXTERNAL_ID=
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
Sau đó, lệnh này sẽ được chuyển vào lịch sử (đồng thời, ít nhất trạng thái, thời gian thực hiện và ID vị thế sẽ thay đổi).
HistoryOrderCache added: 1311792104
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
0 ORDER_TIME_SETUP=2022.04.11 12:34:51
1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
2 ORDER_TIME_DONE=2022.04.11 12:34:51
3 ORDER_TYPE=ORDER_TYPE_BUY
4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
5 ORDER_TYPE_TIME=ORDER_TIME_GTC
6 ORDER_STATE=ORDER_STATE_FILLED
7 ORDER_MAGIC=0
8 ORDER_POSITION_ID=1311792104
9 ORDER_TIME_SETUP_MSC=2022.04.11 12:34:51'096
10 ORDER_TIME_DONE_MSC=2022.04.11 12:34:51'097
11 ORDER_POSITION_BY_ID=0
12 ORDER_TICKET=1311792104
13 ORDER_REASON=ORDER_REASON_CLIENT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
0 ORDER_VOLUME_INITIAL=0.01
1 ORDER_VOLUME_CURRENT=0.0
2 ORDER_PRICE_OPEN=1.09218
3 ORDER_PRICE_CURRENT=1.09218
4 ORDER_PRICE_STOPLIMIT=0.0
5 ORDER_SL=0.0
6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
0 ORDER_SYMBOL=EURUSD
1 ORDER_COMMENT=
2 ORDER_EXTERNAL_ID=
>>> positions: 0, orders: 1, history: 1
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
Lưu ý rằng những sửa đổi này diễn ra trong cùng một lần gọi của OnTrade
. Nói cách khác, trong khi chương trình của chúng ta đang phân tích các thuộc tính của lệnh mới (bằng cách gọi orders.scan
), lệnh đã được terminal xử lý song song, và đến khi lịch sử được kiểm tra (bằng cách gọi history.scan
), nó đã được ghi vào lịch sử. Đó là lý do tại sao nó được liệt kê cả ở đây lẫn ở kia theo dòng cuối cùng của đoạn nhật ký này. Hành vi này là bình thường đối với các chương trình đa luồng và nên được xem xét khi thiết kế chúng. Nhưng không phải lúc nào cũng vậy. Ở đây chúng ta chỉ đang thu hút sự chú ý đến nó. Khi thực thi một chương trình MQL nhanh chóng, tình huống này thường không xảy ra.
Nếu chúng ta kiểm tra lịch sử trước, rồi sau đó là các lệnh trực tuyến, thì ở giai đoạn đầu tiên chúng ta có thể thấy rằng lệnh chưa có trong lịch sử, và ở giai đoạn thứ hai rằng lệnh không còn trực tuyến nữa. Tức là, nó có thể tạm thời bị mất trong một khoảnh khắc. Một tình huống thực tế hơn là bỏ qua một lệnh trong giai đoạn hoạt động của nó do đồng bộ hóa lịch sử, tức là ngay lập tức ghi nhận nó lần đầu tiên trong lịch sử.
Nhắc lại rằng MQL5 không cho phép đồng bộ hóa toàn bộ môi trường giao dịch, mà chỉ từng phần:
- Trong số các lệnh đang hoạt động, thông tin là phù hợp cho lệnh mà hàm
OrderSelect
hoặcOrderGetTicket
vừa được gọi. - Trong số các vị thế, thông tin là phù hợp cho vị thế mà hàm
PositionSelect
,PositionSelectByTicket
, hoặcPositionGetTicket
vừa được gọi. - Đối với các lệnh và giao dịch trong lịch sử, thông tin có sẵn trong bối cảnh của lần gọi cuối cùng của
HistorySelect
,HistorySelectByPosition
,HistoryOrderSelect
,HistoryDealSelect
.
Ngoài ra, hãy nhớ rằng các sự kiện giao dịch (như bất kỳ sự kiện MQL5 nào) là các thông báo về các thay đổi đã xảy ra, được đặt trong hàng đợi, và được lấy ra từ hàng đợi một cách trì hoãn, không phải ngay tại thời điểm thay đổi. Hơn nữa, sự kiện OnTrade
xảy ra sau các sự kiện OnTradeTransaction
liên quan.
Hãy thử các cấu hình chương trình khác nhau, gỡ lỗi, và tạo nhật ký chi tiết để chọn thuật toán đáng tin cậy nhất cho hệ thống giao dịch của bạn.
Quay lại nhật ký của chúng ta. Khi kích hoạt tiếp theo của OnTrade
, tình hình đã được sửa chữa: bộ nhớ cache của các lệnh đang hoạt động đã phát hiện việc xóa lệnh. Đồng thời, bộ nhớ cache vị thế đã thấy một vị thế mở.
>>> OnTrade(2)
PositionCache added: 1311792104
MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
ENUM_POSITION_PROPERTY_INTEGER Count=9
0 POSITION_TIME=2022.04.11 12:34:51
1 POSITION_TYPE=POSITION_TYPE_BUY
2 POSITION_MAGIC=0
3 POSITION_IDENTIFIER=1311792104
4 POSITION_TIME_MSC=2022.04.11 12:34:51'097
5 POSITION_TIME_UPDATE=2022.04.11 12:34:51
6 POSITION_TIME_UPDATE_MSC=2022.04.11 12:34:51'097
7 POSITION_TICKET=1311792104
8 POSITION_REASON=POSITION_REASON_CLIENT
ENUM_POSITION_PROPERTY_DOUBLE Count=8
0 POSITION_VOLUME=0.01
1 POSITION_PRICE_OPEN=1.09218
2 POSITION_PRICE_CURRENT=1.09214
3 POSITION_SL=0.00000
4 POSITION_TP=0.00000
5 POSITION_COMMISSION=0.0
6 POSITION_SWAP=0.00
7 POSITION_PROFIT=-0.04
ENUM_POSITION_PROPERTY_STRING Count=3
0 POSITION_SYMBOL=EURUSD
1 POSITION_COMMENT=
2 POSITION_EXTERNAL_ID=
OrderCache removed: 1311792104
>>> positions: 1, orders: 0, history: 1
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
Sau một thời gian, chúng ta đóng vị thế. Vì trong mã của chúng ta, bộ nhớ cache vị thế được kiểm tra trước (positions.scan
), các thay đổi đối với vị thế đã đóng được đưa vào nhật ký.
>>> OnTrade(8)
PositionCache changed: 1311792104
POSITION_PRICE_CURRENT: 1.09214 -> 1.09222
POSITION_PROFIT: -0.04 -> 0.04
2
3
4
Tiếp theo trong cùng lần gọi của OnTrade
, chúng ta phát hiện sự xuất hiện của một lệnh đóng và việc chuyển ngay lập tức của nó vào lịch sử (một lần nữa, do xử lý song song nhanh chóng bởi terminal).
OrderCache added: 1311796883
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
0 ORDER_TIME_SETUP=2022.04.11 12:39:55
1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
2 ORDER_TIME_DONE=1970.01.01 00:00:00
3 ORDER_TYPE=ORDER_TYPE_SELL
4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
5 ORDER_TYPE_TIME=ORDER_TIME_GTC
6 ORDER_STATE=ORDER_STATE_STARTED
7 ORDER_MAGIC=0
8 ORDER_POSITION_ID=1311792104
9 ORDER_TIME_SETUP_MSC=2022.04.11 12:39:55'710
10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000
11 ORDER_POSITION_BY_ID=0
12 ORDER_TICKET=1311796883
13 ORDER_REASON=ORDER_REASON_CLIENT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
0 ORDER_VOLUME_INITIAL=0.01
1 ORDER_VOLUME_CURRENT=0.01
2 ORDER_PRICE_OPEN=1.09222
3 ORDER_PRICE_CURRENT=1.09222
4 ORDER_PRICE_STOPLIMIT=0.0
5 ORDER_SL=0.0
6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
0 ORDER_SYMBOL=EURUSD
1 ORDER_COMMENT=
2 ORDER_EXTERNAL_ID=
HistoryOrderCache added: 1311796883
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
0 ORDER_TIME_SETUP=2022.04.11 12:39:55
1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
2 ORDER_TIME_DONE=2022.04.11 12:39:55
3 ORDER_TYPE=ORDER_TYPE_SELL
4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
5 ORDER_TYPE_TIME=ORDER_TIME_GTC
6 ORDER_STATE=ORDER_STATE_FILLED
7 ORDER_MAGIC=0
8 ORDER_POSITION_ID=1311792104
9 ORDER_TIME_SETUP_MSC=2022.04.11 12:39:55'710
10 ORDER_TIME_DONE_MSC=2022.04.11 12:39:55'711
11 ORDER_POSITION_BY_ID=0
12 ORDER_TICKET=1311796883
13 ORDER_REASON=ORDER_REASON_CLIENT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
0 ORDER_VOLUME_INITIAL=0.01
1 ORDER_VOLUME_CURRENT=0.0
2 ORDER_PRICE_OPEN=1.09222
3 ORDER_PRICE_CURRENT=1.09222
4 ORDER_PRICE_STOPLIMIT=0.0
5 ORDER_SL=0.0
6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
0 ORDER_SYMBOL=EURUSD
1 ORDER_COMMENT=
2 ORDER_EXTERNAL_ID=
>>> positions: 1, orders: 1, history: 2
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
Hiện đã có 2 lệnh trong bộ nhớ cache lịch sử, nhưng bộ nhớ cache vị thế và lệnh đang hoạt động được phân tích trước bộ nhớ cache lịch sử chưa áp dụng những thay đổi này.
Nhưng trong sự kiện OnTrade
tiếp theo, chúng ta thấy rằng vị thế đã được đóng, và lệnh thị trường đã biến mất.
>>> OnTrade(9)
PositionCache removed: 1311792104
OrderCache removed: 1311796883
>>> positions: 0, orders: 0, history: 2
2
3
4
Nếu chúng ta giám sát các bộ nhớ cache trên mỗi tick (hoặc một lần mỗi giây, nhưng không chỉ cho các sự kiện OnTrade
), chúng ta sẽ thấy các thay đổi trong thuộc tính ORDER_PRICE_CURRENT
và POSITION_PRICE_CURRENT
diễn ra liên tục. POSITION_PROFIT
cũng sẽ thay đổi.
Các lớp của chúng ta không có tính persistence
, tức là chúng chỉ tồn tại trong RAM và không biết cách lưu và khôi phục trạng thái của chúng trong bất kỳ bộ nhớ dài hạn nào, chẳng hạn như tệp. Điều này có nghĩa là chương trình có thể bỏ lỡ một thay đổi xảy ra giữa các phiên terminal. Nếu bạn cần chức năng như vậy, bạn nên tự triển khai nó. Trong tương lai, ở Phần 7 của cuốn sách, chúng ta sẽ xem xét hỗ trợ cơ sở dữ liệu SQLite tích hợp trong MQL5, cung cấp cách hiệu quả và tiện lợi nhất để lưu trữ bộ nhớ cache môi trường giao dịch và dữ liệu bảng tương tự.