Ứng dụng của tài nguyên đồ họa trong giao dịch
Tất nhiên, việc làm đẹp không phải là mục đích chính của các tài nguyên. Hãy cùng xem cách tạo một công cụ hữu ích dựa trên chúng. Chúng ta cũng sẽ loại bỏ một thiếu sót nữa: cho đến nay, chúng ta chỉ sử dụng tài nguyên bên trong các đối tượng OBJ_BITMAP_LABEL
, được định vị theo tọa độ màn hình. Tuy nhiên, các tài nguyên đồ họa cũng có thể được nhúng vào các đối tượng OBJ_BITMAP
với tham chiếu đến tọa độ báo giá: giá và thời gian.
Trước đó trong cuốn sách, chúng ta đã thấy chỉ báo IndDeltaVolume.mq5
tính toán khối lượng delta (tick hoặc thực) cho mỗi thanh. Ngoài cách biểu diễn khối lượng delta này, còn có một cách khác không kém phần phổ biến với người dùng: hồ sơ thị trường. Đây là sự phân bố khối lượng theo các mức giá. Biểu đồ histogram như vậy có thể được xây dựng cho toàn bộ cửa sổ, cho một độ sâu nhất định (ví dụ, trong một ngày), hoặc cho một thanh duy nhất.
Chính tùy chọn cuối cùng này sẽ được chúng ta triển khai dưới dạng một chỉ báo mới DeltaVolumeProfile.mq5
. Chúng ta đã xem xét các chi tiết kỹ thuật chính của việc yêu cầu lịch sử tick trong khuôn khổ của chỉ báo trên, vì vậy giờ đây chúng ta sẽ tập trung chủ yếu vào thành phần đồ họa.
Cờ ShowSplittedDelta
trong biến đầu vào sẽ kiểm soát cách hiển thị khối lượng: phân chia theo hướng mua/bán hay gộp lại.
input bool ShowSplittedDelta = true;
Sẽ không có bộ đệm trong chỉ báo. Nó sẽ tính toán và hiển thị biểu đồ histogram cho một thanh cụ thể theo yêu cầu của người dùng, cụ thể là bằng cách nhấp vào thanh này. Do đó, chúng ta sẽ sử dụng trình xử lý OnChartEvent
. Trong trình xử lý này, chúng ta lấy tọa độ màn hình, tính toán lại thành giá và thời gian, và gọi một hàm trợ giúp RequestData
, hàm này bắt đầu quá trình tính toán.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_CLICK)
{
datetime time;
double price;
int window;
ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price);
time += PeriodSeconds() / 2;
const int b = iBarShift(_Symbol, _Period, time, true);
if(b != -1 && window == 0)
{
RequestData(b, iTime(_Symbol, _Period, b));
}
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Để điền dữ liệu, chúng ta cần lớp DeltaVolumeProfile
, được xây dựng tương tự như lớp CalcDeltaVolume
từ IndDeltaVolume.mq5
.
Lớp mới mô tả các biến考虑 phương pháp tính toán khối lượng (tickType
), loại giá mà biểu đồ được xây dựng (barType
), chế độ từ biến đầu vào ShowSplittedDelta
(sẽ được đặt trong biến thành viên delta
), cũng như tiền tố cho các đối tượng được tạo trên biểu đồ.
class DeltaVolumeProfile
{
const COPY_TICKS tickType;
const ENUM_SYMBOL_CHART_MODE barType;
const bool delta;
static const string prefix;
...
public:
DeltaVolumeProfile(const COPY_TICKS type, const bool d) :
tickType(type), delta(d),
barType((ENUM_SYMBOL_CHART_MODE)SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE))
{
}
~DeltaVolumeProfile()
{
ObjectsDeleteAll(0, prefix, 0); // TODO: delete resources
}
...
};
static const string DeltaVolumeProfile::prefix = "DVP";
DeltaVolumeProfile deltas(TickType, ShowSplittedDelta);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Loại tick (tick type
) chỉ có thể được thay đổi thành giá trị TRADE_TICKS
cho các công cụ giao dịch có khối lượng thực. Theo mặc định, chế độ INFO_TICKS
được kích hoạt, hoạt động trên tất cả các công cụ.
Các tick cho một thanh cụ thể được yêu cầu bởi phương thức createProfileBar
.
int createProfileBar(const int i)
{
MqlTick ticks[];
const datetime time = iTime(_Symbol, _Period, i);
// prev và next - giới hạn thời gian của thanh
const datetime prev = time;
const datetime next = prev + PeriodSeconds();
ResetLastError();
const int n = CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL,
prev * 1000, next * 1000 - 1);
if(n > -1 && _LastError == 0)
{
calcProfile(i, time, ticks);
}
else
{
return -_LastError;
}
return n;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Phân tích trực tiếp các tick và tính toán khối lượng được thực hiện trong phương thức được bảo vệ calcProfile
. Trong đó, trước tiên, chúng ta tìm ra phạm vi giá của thanh và kích thước của nó tính bằng pixel.
void calcProfile(const int b, const datetime time, const MqlTick &ticks[])
{
const string name = prefix + (string)(ulong)time;
const double high = iHigh(_Symbol, _Period, b);
const double low = iLow(_Symbol, _Period, b);
const double range = high - low;
ObjectCreate(0, name, OBJ_BITMAP, 0, time, high);
int x1, y1, x2, y2;
ChartTimePriceToXY(0, 0, time, high, x1, y1);
ChartTimePriceToXY(0, 0, time, low, x2, y2);
const int h = y2 - y1 + 1;
const int w = (int)(ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)
/ ChartGetInteger(0, CHART_WIDTH_IN_BARS));
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Dựa trên thông tin này, chúng ta tạo một đối tượng OBJ_BITMAP
, cấp phát một mảng cho hình ảnh và tạo một tài nguyên. Nền của toàn bộ hình ảnh là trống (trong suốt). Mỗi đối tượng được neo bởi điểm giữa phía trên vào giá High
của thanh của nó và có chiều rộng bằng một thanh.
uint data[];
ArrayResize(data, w * h);
ArrayInitialize(data, 0);
ResourceCreate(name + (string)ChartID(), data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE);
ObjectSetString(0, name, OBJPROP_BMPFILE, "::" + name + (string)ChartID());
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_UPPER);
...
2
3
4
5
6
7
8
9
10
Tiếp theo là tính toán khối lượng trong các tick của mảng được truyền vào. Số lượng mức giá bằng chiều cao của thanh tính bằng pixel (h
). Thông thường, nó nhỏ hơn phạm vi giá tính bằng điểm, và do đó các pixel đóng vai trò như một loại giỏ để tính toán thống kê. Nếu trên một khung thời gian nhỏ, phạm vi điểm nhỏ hơn kích thước tính bằng pixel, biểu đồ histogram sẽ trông thưa thớt về mặt thị giác. Khối lượng mua và bán được tích lũy riêng biệt trong các mảng plus
và minus
.
long plus[], minus[], max = 0;
ArrayResize(plus, h);
ArrayResize(minus, h);
ArrayInitialize(plus, 0);
ArrayInitialize(minus, 0);
const int n = ArraySize(ticks);
for(int j = 0; j < n; ++j)
{
const double p1 = price(ticks[j]); // trả về Bid hoặc Last
const int index = (int)((high - p1) / range * (h - 1));
if(tickType == TRADE_TICKS)
{
// nếu có khối lượng thực, chúng ta có thể tính đến chúng
if((ticks[j].flags & TICK_FLAG_BUY) != 0)
{
plus[index] += (long)ticks[j].volume;
}
if((ticks[j].flags & TICK_FLAG_SELL) != 0)
{
minus[index] += (long)ticks[j].volume;
}
}
else // tickType == INFO_TICKS hoặc tickType == ALL_TICKS
if(j > 0)
{
// nếu không có khối lượng thực,
// chuyển động giá lên/xuống là ước lượng của loại khối lượng
if((ticks[j].flags & (TICK_FLAG_ASK | TICK_FLAG_BID)) != 0)
{
const double d = (((ticks[j].ask + ticks[j].bid)
- (ticks[j - 1].ask + ticks[j - 1].bid)) / _Point);
if(d > 0) plus[index] += (long)d;
else minus[index] -= (long)d;
}
}
...
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
Để chuẩn hóa biểu đồ histogram, chúng ta tìm giá trị tối đa.
if(delta)
{
if(plus[index] > max) max = plus[index];
if(minus[index] > max) max = minus[index];
}
else
{
if(fabs(plus[index] - minus[index]) > max)
max = fabs(plus[index] - minus[index]);
}
}
...
2
3
4
5
6
7
8
9
10
11
12
Cuối cùng, số liệu thống kê kết quả được xuất ra bộ đệm đồ họa data
và gửi đến tài nguyên. Khối lượng mua được hiển thị bằng màu xanh dương, còn khối lượng bán được hiển thị bằng màu đỏ. Nếu chế độ net được bật, thì số lượng được hiển thị bằng màu xanh lá cây.
for(int i = 0; i < h; i++)
{
if(delta)
{
const int dp = (int)(plus[i] * w / 2 / max);
const int dm = (int)(minus[i] * w / 2 / max);
for(int j = 0; j < dp; j++)
{
data[i * w + w / 2 + j] = ColorToARGB(clrBlue);
}
for(int j = 0; j < dm; j++)
{
data[i * w + w / 2 - j] = ColorToARGB(clrRed);
}
}
else
{
const int d = (int)((plus[i] - minus[i]) * w / 2 / max);
const int sign = d > 0 ? +1 : -1;
for(int j = 0; j < fabs(d); j++)
{
data[i * w + w / 2 + j * sign] = ColorToARGB(clrGreen);
}
}
}
ResourceCreate(name + (string)ChartID(), data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE);
}
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
Bây giờ chúng ta có thể quay lại hàm RequestData
: nhiệm vụ của nó là gọi phương thức createProfileBar
và xử lý lỗi (nếu có).
void RequestData(const int b, const datetime time, const int count = 0)
{
Comment("Requesting ticks for ", time);
if(deltas.createProfileBar(b) <= 0)
{
Print("No data on bar ", b, ", at ", TimeToString(time),
". Sending event for refresh...");
ChartSetSymbolPeriod(0, _Symbol, _Period); // yêu cầu cập nhật biểu đồ
EventChartCustom(0, TRY_AGAIN, b, count + 1, NULL);
}
Comment("");
}
2
3
4
5
6
7
8
9
10
11
12
Chiến lược xử lý lỗi duy nhất là thử yêu cầu lại các tick vì chúng có thể chưa kịp tải. Để làm điều này, hàm gửi một thông điệp tùy chỉnh TRY_AGAIN
đến biểu đồ và tự xử lý nó.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
else if(id == CHARTEVENT_CUSTOM + TRY_AGAIN)
{
Print("Refreshing... ", (int)dparam);
const int b = (int)lparam;
if((int)dparam < 5)
{
RequestData(b, iTime(_Symbol, _Period, b), (int)dparam);
}
else
{
Print("Give up. Check tick history manually, please, then click the bar again");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Chúng ta lặp lại quá trình này không quá 5 lần, vì lịch sử tick có thể có độ sâu giới hạn, và việc tải máy tính mà không có lý do là không hợp lý.
Lớp DeltaVolumeProfile
cũng có cơ chế xử lý thông điệp CHARTEVENT_CHART_CHANGE
để vẽ lại các đối tượng hiện có trong trường hợp thay đổi kích thước hoặc tỷ lệ của biểu đồ. Chi tiết có thể được tìm thấy trong mã nguồn.
Kết quả của chỉ báo được hiển thị trong hình ảnh sau.
Hiển thị biểu đồ histogram theo từng thanh của các khối lượng riêng biệt trong tài nguyên đồ họa
Lưu ý rằng các biểu đồ histogram không được hiển thị ngay sau khi vẽ chỉ báo: bạn phải nhấp vào thanh để tính toán biểu đồ histogram của nó.