forked from rosh/MQL5Book
275 lines
9.3 KiB
MQL5
275 lines
9.3 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| CalendarTrading.mq5 |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#property copyright "2022, MetaQuotes Ltd."
| |
#property link "https://www.mql5.com"
| |
#property description "Trade by calendar events in the tester or online."
| |
#property tester_file "xyz.cal"
| |
| |
#define SHOW_WARNINGS // output extended info into the log, with changes in data state
| |
#define WARNING Print // use simple Print for warnings (instead of a built-in format with line numbers etc.)
| |
#define LOGGING // calendar detailed logs
| |
#include "..\..\Include\MqlTradeSync.mqh"
| |
#include "..\..\Include\PositionFilter.mqh"
| |
#include "..\..\Include\TrailingStop.mqh"
| |
#include "..\..\Include\CalendarFilterCached.mqh"
| |
#include "..\..\Include\StringUtils.mqh"
| |
#include "..\..\Include\DealFilter.mqh"
| |
#include "..\..\Include\Tuples.mqh"
| |
| |
input double Volume; // Volume (0 = minimal lot)
| |
input int Distance2SLTP = 500; // Distance to SL/TP in points (0 = no)
| |
input uint MultiplePositions = 25;
| |
sinput ulong EventID;
| |
sinput string Text;
| |
| |
AutoPtr<CalendarFilter> fptr;
| |
AutoPtr<CalendarCache> cache;
| |
AutoPtr<TrailingStop> trailing[];
| |
| |
double Lot;
| |
bool Hedging;
| |
string Base;
| |
string Profit;
| |
| |
//+------------------------------------------------------------------+
| |
//| Expert initialization function |
| |
//+------------------------------------------------------------------+
| |
int OnInit()
| |
{
| |
if(AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO)
| |
{
| |
Alert("This is a test EA! Run it on a DEMO account only!");
| |
return INIT_FAILED;
| |
}
| |
| |
Lot = Volume == 0 ? SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) : Volume;
| |
Hedging = AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;
| |
Base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE);
| |
Profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
| |
| |
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 event is given, use it
| |
if(EventID > 0) f.let(EventID);
| |
else
| |
{
| |
// otherwise track news for current chart currencies only
| |
f.let(Base);
| |
if(Base != Profit)
| |
{
| |
f.let(Profit);
| |
}
| |
| |
// monitor high impact economic indicators with available forecasts
| |
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);
| |
}
| |
// setup timer for periodic trade by news
| |
EventSetTimer(1);
| |
return INIT_SUCCEEDED;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Custom tester event handler |
| |
//+------------------------------------------------------------------+
| |
double OnTester()
| |
{
| |
Print("Trade profits by calendar events:");
| |
HistorySelect(0, LONG_MAX);
| |
DealFilter filter;
| |
int props[] = {DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_MAGIC, DEAL_TIME};
| |
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);
| |
Tuple5<double, double, double, ulong, 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);
| |
}
| |
}
| |
| |
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;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Timer event handler |
| |
//+------------------------------------------------------------------+
| |
void OnTimer()
| |
{
| |
CalendarFilter *f = fptr[];
| |
MqlCalendarValue records[];
| |
| |
f.let(TimeTradeServer() - SCOPE_DAY, TimeTradeServer() + SCOPE_DAY);
| |
| |
if(f.update(records)) // find changes that match filters
| |
{
| |
// print changes to 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));
| |
}
| |
| |
// calculate news impact
| |
static const int impacts[3] = {0, +1, -1};
| |
int impact = 0;
| |
string about = "";
| |
ulong lasteventid = 0;
| |
for(int i = 0; i < ArraySize(records); ++i)
| |
{
| |
const int sign = result[i * p + 1] == Profit ? -1 : +1;
| |
impact += sign * impacts[records[i].impact_type];
| |
about += StringFormat("%+lld ", sign * (long)records[i].event_id);
| |
lasteventid = records[i].event_id;
| |
}
| |
| |
if(impact == 0) return; // no signal
| |
| |
// close existing positions if needed
| |
PositionFilter positions;
| |
ulong tickets[];
| |
positions.let(POSITION_SYMBOL, _Symbol).select(tickets);
| |
const int n = ArraySize(tickets);
| |
| |
if(n >= (int)(Hedging ? MultiplePositions : 1))
| |
{
| |
MqlTradeRequestSync position;
| |
position.close(_Symbol) && position.completed();
| |
}
| |
| |
// open new position according to the signal direction
| |
MqlTradeRequestSync request;
| |
request.magic = lasteventid;
| |
request.comment = about;
| |
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
| |
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
| |
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
| |
ulong ticket = 0;
| |
| |
if(impact > 0)
| |
{
| |
ticket = request.buy(Lot, 0,
| |
Distance2SLTP ? ask - point * Distance2SLTP : 0,
| |
Distance2SLTP ? ask + point * Distance2SLTP : 0);
| |
}
| |
else if(impact < 0)
| |
{
| |
ticket = request.sell(Lot, 0,
| |
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) // find free slot, create trailing object
| |
{
| |
trailing[i] = new TrailingStop(ticket, Distance2SLTP, Distance2SLTP / 50);
| |
break;
| |
}
| |
}
| |
}
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Tick event handler |
| |
//+------------------------------------------------------------------+
| |
void OnTick()
| |
{
| |
for(int i = 0; i < ArraySize(trailing); ++i)
| |
{
| |
if(trailing[i][])
| |
{
| |
if(!trailing[i][].trail()) // position was closed
| |
{
| |
trailing[i] = NULL; // free the slot, delete object
| |
}
| |
}
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
|