Giao dịch dựa trên lịch kinh tế
Có nhiều chiến lược giao dịch tin tức: sử dụng lệnh thị trường hoặc lệnh chờ, phân tích các chỉ số tài chính (hướng di chuyển giá), hoặc không phân tích (bắt biến động). Ngoài ra, việc thêm bộ lọc chống tin tức vào nhiều hệ thống giao dịch khác cũng rất hữu ích. Việc tối ưu hóa và gỡ lỗi các chương trình này rất khó vì lịch MQL5 không có sẵn trong tester. Tuy nhiên, với bộ nhớ đệm được phát triển ở phần trước, chúng ta có thể khắc phục tình trạng này.
Hãy thử tạo một Expert Advisor sẽ tham gia thị trường khi có tin tức được công bố, dựa trên đánh giá tác động của chúng lên giá. Tệp bộ nhớ đệm xyz.cal
vừa được tạo bằng chỉ báo CalendarMonitorCached.mq5
.
Hãy nhớ rằng hình ảnh lịch trong bộ nhớ đệm luôn tương ứng với thời điểm lưu và cần thận trọng khi đọc: đối với các sự kiện sau này, chỉ số thực tế chưa biết, và các sự kiện xa hơn có thể không tồn tại. Bạn nên cập nhật tệp bộ nhớ đệm lịch định kỳ trước khi tối ưu hóa hoặc kiểm tra tiếp theo.
Nếu cần, cũng cần xem xét cài đặt thời gian DST trong năm: nếu chế độ DST của sự kiện khác với DST tại thời điểm lưu trữ lịch, bạn cần điều chỉnh thời gian sớm hoặc muộn 1 giờ. Bạn có thể tránh những khó khăn này bằng cách chọn broker không dùng DST hoặc xây dựng chiến lược trên khung thời gian lớn hơn H1.
Expert Advisor CalendarTrading.mq5
sẽ chỉ giao dịch các sự kiện tin tức thỏa mãn:
- Liên quan đến biểu tượng đang hoạt động trên biểu đồ
- Có loại chỉ số tài chính (tức là định lượng)
- Độ quan trọng cao
- Vừa nhận được giá trị hiện tại của chỉ số
Điều cuối cùng rất quan trọng vì đối với các chỉ số có giá trị dự báo và thực tế, hệ thống đặt giá trị của trường impact_type
tương ứng: nó sẽ đóng vai trò tín hiệu giao dịch (chỉ ra hướng vào thị trường).
Thời gian chính xác của việc công bố tin tức thường không trùng với thời gian dự kiến trong trường
MqlCalendarValue::time
. Lịch không ghi lại thời gian này, và nó cũng không có trong bộ nhớ đệm. Do đó, độ chính xác của việc kiểm tra chiến lược tin tức có thể bị ảnh hưởng. Nếu bạn muốn đưa phân tích và ra quyết định gần với quy trình trực tuyến, hãy tích lũy thống kê công bố tin tức bằng dịch vụ nhưCalendarChangeSaver.mq5
và nhúng nó vào bộ nhớ đệm.
Mặc định, giao dịch được thực hiện với lô tối thiểu, với mức chốt lời và cắt lỗ được đặt ở khoảng cách tính bằng điểm. Tất cả được phản ánh trong các tham số đầu vào.
input double Volume; // Volume (0 = minimal lot)
input int Distance2SLTP = 500; // Distance to SL/TP in points (0 = no)
input uint MultiplePositions = 25;
2
3
Đối với tài khoản hedging, chúng ta cho phép tồn tại đồng thời nhiều vị thế, mặc định là 25. Đây là môi trường kiểm tra được khuyến nghị vì nó cho phép đánh giá độc lập lợi nhuận của giao dịch song song trên các loại tin tức khác nhau (mỗi vị thế được tạo độc lập và không dẫn đến đóng vị thế khác). Ngược lại, duy trì chỉ một vị thế sẽ tự động triệt tiêu các tín hiệu mâu thuẫn từ các tin tức khác nhau.
Tùy chọn, Expert Advisor hỗ trợ bộ lọc cho định danh loại tin tức và văn bản để tìm kiếm theo tiêu đề.
sinput ulong EventID;
sinput string Text;
2
Điều này có thể hữu ích cho nghiên cứu sau này về các tin tức cụ thể.
Ở cấp độ toàn cục, các con trỏ đối tượng được mô tả để xử lý phân tích tin tức và theo dõi vị thế.
AutoPtr<CalendarFilter> fptr;
AutoPtr<CalendarCache> cache;
AutoPtr<TrailingStop> trailing[];
2
3
Chế độ hoạt động và cặp tiền tệ của biểu tượng hiện tại được lưu trong các biến tương ứng. Để đơn giản, giả định sử dụng trên Forex (trên các thị trường khác, bạn sẽ giao dịch bằng một loại tiền - tiền định giá của ticker).
const bool Hedging =
AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;
const string Base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE);
const string Profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
2
3
4
Trong trình xử lý OnInit
, chúng ta tải bộ nhớ đệm lịch và cấu hình các bộ lọc như mô tả ở trên. Việc không có bộ nhớ đệm được phép trên biểu đồ trực tuyến: lúc đó Expert Advisor hoạt động ở chế độ thực chiến, trực tiếp với lịch. Trong tester, không có tệp bộ nhớ đệm sẽ ngăn Expert Advisor khởi động.
int OnInit()
{
cache = new CalendarCache("xyz.cal", true);
if(cache[].isLoaded())
{
fptr = new CalendarFilterCached(cache[]);
}
else
{
if(!MQLInfoInteger(MQL_TESTER))
{
Print("Calendar cache file not found, fall back to online mode");
fptr = new CalendarFilter();
}
else
{
Print("Can't proceed in the tester without calendar cache file");
return INIT_FAILED;
}
}
CalendarFilter *f = fptr[];
if(!f.isLoaded()) return INIT_FAILED;
// if a specific type of event is set, we look only at it
if(EventID > 0) f.let(EventID);
else
{
// otherwise follow the news on the currencies of the current symbol
f.let(Base);
if(Base != Profit)
{
f.let(Profit);
}
// financial indicators, high importance, actual value
f.let(CALENDAR_TYPE_INDICATOR);
f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, NOT_EQUAL);
f.let(CALENDAR_IMPORTANCE_HIGH);
if(StringLen(Text)) f.let(Text);
}
f.describe();
if(Distance2SLTP)
{
ArrayResize(trailing, Hedging && MultiplePositions ? MultiplePositions : 1);
}
// check the news filter and start trading on it by a second timer
EventSetTimer(1);
return INIT_SUCCEEDED;
}
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
Trong trình xử lý OnTimer
, chúng ta yêu cầu các thay đổi tin tức theo các bộ lọc đã cấu hình.
void OnTimer()
{
CalendarFilter *f = fptr[];
MqlCalendarValue records[];
f.let(TimeTradeServer() - SCOPE_DAY, TimeTradeServer() + SCOPE_DAY);
if(f.update(records)) // find changes that undergo filtering
{
// output properties of changed news to the log
static const ENUM_CALENDAR_PROPERTY props[] =
{
CALENDAR_PROPERTY_RECORD_TIME,
CALENDAR_PROPERTY_COUNTRY_CURRENCY,
CALENDAR_PROPERTY_COUNTRY_CODE,
CALENDAR_PROPERTY_EVENT_NAME,
CALENDAR_PROPERTY_EVENT_IMPORTANCE,
CALENDAR_PROPERTY_RECORD_ACTUAL,
CALENDAR_PROPERTY_RECORD_FORECAST,
CALENDAR_PROPERTY_RECORD_PREVISED,
CALENDAR_PROPERTY_RECORD_IMPACT,
};
static const int p = ArraySize(props);
string result[];
f.format(records, props, result);
for(int i = 0; i < ArraySize(result) / p; ++i)
{
Print(SubArrayCombine(result, " | ", i * p, p));
}
...
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
Khi phát hiện các thay đổi phù hợp, chúng được ghi lại như sau (đoạn nhật ký thực tế bên dưới), hiển thị thời gian, tiền tệ, quốc gia, tên, giá trị hiện tại và dự báo, giá trị trước đó, và diễn giải tín hiệu lý thuyết:
...
Filtering 5 records
2021.02.16 13:00 | EUR | EU | Employment Change q/q | HIGH | +0.3 | -0.4 | +1.0 | POSITIVE
2021.02.16 13:00 | EUR | EU | GDP q/q | HIGH | -0.6 | -0.7 | -0.7 | POSITIVE
instant buy 0.01 EURUSD at 1.21638 sl: 1.21138 tp: 1.22138 (1.21637 / 1.21638 / 1.21637)
deal #64 buy 0.01 EURUSD at 1.21638 done (based on order #64)
...
Filtering 3 records
2021.07.06 12:05 | EUR | DE | ZEW Economic Sentiment Indicator | HIGH | +63.3 | +84.1 | +79.8 | NEGATIVE
instant sell 0.01 EURUSD at 1.18473 sl: 1.18973 tp: 1.17973 (1.18473 / 1.18474 / 1.18473)
deal #265 sell 0.01 EURUSD at 1.18473 done (based on order #265)
...
2
3
4
5
6
7
8
9
10
11
12
Tác động tiềm tàng của tin tức lên giá cần được tính dựa trên đánh giá trong trường impact_type
. Điều quan trọng cần lưu ý là chúng ta có hai loại tiền tệ: cơ sở và định giá. Khi tin tức có tác động tích cực đến tiền cơ sở, tỷ giá dự kiến tăng, và nếu tiêu cực, tỷ giá sẽ giảm. Đối với tiền định giá, điều ngược lại xảy ra: tác động tích cực làm tăng giá tiền thứ hai trong cặp, nghĩa là tỷ giá giảm, trong khi tác động tiêu cực làm tăng tỷ giá. Hướng di chuyển giá chuẩn hóa này được tính trong đoạn mã sau bằng biến sign
.
static const ...
...
Distance2SLTP ? bid + point * Distance2SLTP : 0,
Distance2SLTP ? bid - point * Distance2SLTP : 0);
}
if(ticket && request.completed() && Distance2SLTP)
{
for(int i = 0; i < ArraySize(trailing); ++i)
{
if(trailing[i][] == NULL) // looking for a free slot for the position tracking object
{
trailing[i] = new TrailingStop(ticket, Distance2SLTP, Distance2SLTP / 50);
break;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Chúng ta di chuyển các mức dừng lỗ cho tất cả vị thế khi có tick đến.
void OnTick()
{
for(int i = 0; i < ArraySize(trailing); ++i)
{
if(trailing[i][])
{
if(!trailing[i][].trail()) // position was closed
{
trailing[i] = NULL; // Release object and slot
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Điểm thú vị nhất đến đây. Nhờ tester, có thể phân tích sự thành công của chiến lược tin tức không chỉ tổng quát mà còn theo từng tin tức cụ thể. Khối tương ứng được triển khai trong trình xử lý OnTester
. Việc thu thập dữ liệu được thực hiện bằng bộ lọc giao dịch. Sau khi nhận mảng trades
gồm các bộ giá trị báo cáo lợi nhuận, swap, hoa hồng và số magic của mỗi giao dịch, chúng ta tích lũy kết quả trong ba đối tượng MapArray
: chúng tính riêng lợi nhuận, lỗ và số lượng giao dịch cho mỗi số magic
.
double OnTester()
{
Print("Trade profits by calendar events:");
HistorySelect(0, LONG_MAX);
DealFilter filter;
int props[] = {DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_MAGIC};
filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE)
.let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY),
IS::OR_BITWISE);
Tuple4<double, double, double, ulong> trades[];
MapArray<ulong, double> profits;
MapArray<ulong, double> losses;
MapArray<ulong, int> counts;
if(filter.select(props, trades))
{
for(int i = 0; i < ArraySize(trades); ++i)
{
counts.inc((ulong)trades[i]._4);
const double payout = trades[i]._1 + trades[i]._2 + trades[i]._3;
if(payout >= 0)
{
profits.inc((ulong)trades[i]._4, payout);
losses.inc((ulong)trades[i]._4, 0);
}
else
{
profits.inc((ulong)trades[i]._4, 0);
losses.inc((ulong)trades[i]._4, payout);
}
}
...
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
Kết quả là bảng hiển thị thống kê cho từng loại sự kiện theo dòng: định danh, quốc gia, tiền tệ, tổng lợi nhuận hoặc lỗ, số lượng giao dịch (số tin tức), hệ số lợi nhuận, và tên sự kiện.
for(int i = 0; i < profits.getSize(); ++i)
{
MqlCalendarEvent event;
MqlCalendarCountry country;
const ulong keyId = profits.getKey(i);
if(cache[].calendarEventById(keyId, event)
&& cache[].calendarCountryById(event.country_id, country))
{
PrintFormat("%lld %s %s %+.2f [%d] (PF:%.2f) %s",
event.id, country.code, country.currency,
profits[keyId] + losses[keyId], counts[keyId],
profits[keyId] / (losses[keyId] != 0 ? -losses[keyId] : DBL_MIN),
event.name);
}
else
{
Print("undefined ", DoubleToString(profits.getValue(i), 2));
}
}
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Để kiểm tra ý tưởng, hãy chạy Expert Advisor từ đầu năm 2021 (đến giữa 2022) trên cặp EURUSD. Dưới đây là đoạn nhật ký với kết quả từ OnTester
.
Trade profits by calendar events:
840040001 US USD -21.81 [17] (PF:0.53) ISM Manufacturing PMI
840190001 US USD -10.95 [17] (PF:0.69) ADP Nonfarm Employment Change
840200001 US USD -67.09 [78] (PF:0.60) EIA Crude Oil Stocks Change
999030003 EU EUR +14.13 [19] (PF:1.46) Retail Sales m/m
840040003 US USD -17.12 [18] (PF:0.59) ISM Non-Manufacturing PMI
840030016 US USD -1.20 [19] (PF:0.97) Nonfarm Payrolls
840030021 US USD +5.25 [14] (PF:1.21) JOLTS Job Openings
840020010 US USD -14.63 [17] (PF:0.63) Retail Sales m/m
276070001 DE EUR -22.71 [17] (PF:0.47) ZEW Economic Sentiment Indicator
840020005 US USD +10.76 [18] (PF:1.37) Building Permits
840120001 US USD -20.78 [17] (PF:0.49) Existing Home Sales
276030003 DE EUR +18.57 [17] (PF:1.87) Ifo Business Climate
840180002 US USD -3.22 [14] (PF:0.89) CB Consumer Confidence Index
840020014 US USD -8.74 [16] (PF:0.74) Core Durable Goods Orders m/m
840020008 US USD -14.54 [16] (PF:0.63) New Home Sales
250010005 FR EUR +0.66 [10] (PF: SO1.03) GDP q/q
840010007 US USD +0.99 [15] (PF:1.04) GDP q/q
840120003 US USD +4.53 [18] (PF:1.15) Pending Home Sales m/m
276010008 DE EUR -0.72 [10] (PF:0.97) GDP q/q
999030016 EU EUR -14.04 [14] (PF:0.59) GDP q/q
999030001 EU EUR +1.30 [2] (PF:1.35) Employment Change q/q
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Kết quả không quá ấn tượng. Tuy nhiên, giao dịch tin tức đầy tính chủ quan. Thứ nhất, đánh giá lý thuyết về tác động của giá trị thực tế của tin tức lên tỷ giá có thể khác với kỳ vọng cảm xúc của đám đông hoặc thông tin nền bổ sung (nằm ngoài lịch và không định lượng được). Thứ hai, chúng ta đã đề cập đến sự không chính xác trong thời gian công bố giá trị thực tế. Thứ ba, chiến lược của chúng ta được triển khai ở dạng đơn giản nhất, không phân tích chuyển động giá sơ bộ (khi có thể đã có rò rỉ và tin tức đã được "chơi" trước đó).
Nhìn chung, kiểm tra này cho thấy các báo cáo yêu thích của trader như Nonfarm Payrolls hoặc GDP không đảm bảo thành công, ít nhất không với cài đặt mặc định của chúng ta. Tiếp theo, cần phân tích từng giao dịch riêng lẻ theo cách thông thường, tìm hiểu điều gì sai, chọn tham số và cải thiện thuật toán, đặc biệt là thêm mô-đun điều chỉnh thời gian cho việc chuyển đổi DST trong múi giờ máy chủ.
Đồng thời, kỹ thuật này hoạt động tốt, và chúng ta có thể thử chọn những tin tức thành công nhất để bắt đầu. Chẳng hạn, hãy lấy tin tức 276030003 (Ifo Business Climate). Bằng cách đặt nó vào EventID
, chúng ta sẽ nhận được báo cáo sau, trùng với các chỉ số đã tính toán.
Báo cáo giao dịch trong tester dựa trên tin tức Ifo Business Climate
Bạn cũng có thể thử giao dịch trên một nhóm sự kiện tương tự. Đặc biệt, để chỉ phản ứng với tin tức GDP (của các quốc gia khác nhau), nhập chuỗi *GDP*
vào biến Text
. Dấu sao được thêm vào vì nếu không có chúng, chuỗi 3 ký tự sẽ được lớp bộ lọc coi là tiền tệ. Chuỗi có độ dài bất kỳ ngoài 2 (mã quốc gia) hoặc 3 (mã tiền tệ) có thể được chỉ định nguyên trạng, ví dụ, farm
, Nonfarm
, Sales
— chúng sẽ được bộ lọc tìm kiếm dưới dạng chuỗi con của tên, phân biệt chữ hoa/thường.