forked from rosh/MQL5Book
244 lines
9.4 KiB
MQL5
244 lines
9.4 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| OCO2.mq5 |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#property copyright "2022, MetaQuotes Ltd."
| |
#property link "https://www.mql5.com"
| |
#property description "Implemenation of OCO (One Cancels Other) strategy with 2 pending stop orders."
| |
| |
#include "..\..\Include\OrderFilter.mqh"
| |
#include "..\..\Include\PositionFilter.mqh"
| |
#include "..\..\Include\SymbolMonitor.mqh"
| |
| |
#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.)
| |
#include "..\..\Include\MqlTradeSync.mqh"
| |
| |
enum EVENT_TYPE
| |
{
| |
ON_TRANSACTION, // OnTradeTransaction
| |
ON_TRADE // OnTrade
| |
};
| |
| |
input double Volume; // Volume (0 - minimal lot)
| |
input uint Distance2SLTP = 500; // Distance Indent/SL/TP (points)
| |
input ulong Magic = 1234567890;
| |
input ulong Deviation = 10;
| |
input ulong Expiration = 0; // Expiration (seconds in future, 3600 - 1 hour, etc)
| |
input EVENT_TYPE ActivationBy = ON_TRANSACTION;
| |
| |
//+------------------------------------------------------------------+
| |
//| Custom struct for implicit initialization of many fields |
| |
//+------------------------------------------------------------------+
| |
struct MqlTradeRequestSyncOCO: public MqlTradeRequestSync
| |
{
| |
MqlTradeRequestSyncOCO()
| |
{
| |
symbol = _Symbol;
| |
magic = Magic;
| |
deviation = Deviation;
| |
if(Expiration > 0)
| |
{
| |
type_time = ORDER_TIME_SPECIFIED;
| |
expiration = (datetime)(TimeCurrent() + Expiration);
| |
}
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Global variables to track trade environment |
| |
//+------------------------------------------------------------------+
| |
OrderFilter orders; // helper order finder
| |
PositionFilter trades; // helper position finder
| |
bool FirstTick = false; // process OnTick only once
| |
ulong ExecutionCount = 0; // how many times RunStrategy() was called
| |
| |
//+------------------------------------------------------------------+
| |
//| 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;
| |
}
| |
| |
FirstTick = true;
| |
| |
orders.let(ORDER_MAGIC, Magic).let(ORDER_SYMBOL, _Symbol)
| |
.let(ORDER_TYPE, (1 << ORDER_TYPE_BUY_STOP) | (1 << ORDER_TYPE_SELL_STOP), IS::OR_BITWISE);
| |
trades.let(POSITION_MAGIC, Magic).let(POSITION_SYMBOL, _Symbol);
| |
| |
return INIT_SUCCEEDED;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Main trading function |
| |
//+------------------------------------------------------------------+
| |
void RunStrategy()
| |
{
| |
ExecutionCount++;
| |
| |
ulong tickets[];
| |
ulong states[];
| |
| |
orders.select(ORDER_STATE, tickets, states);
| |
const int n = ArraySize(tickets);
| |
if(n == 2) return; // OK - standard state
| |
| |
if(n > 0) // 1 or 2+ in both cases need to remove them
| |
{
| |
if(n > 2)
| |
{
| |
Alert("Too many orders found: " + (string)n);
| |
}
| |
| |
// remove all related orders
| |
MqlTradeRequestSyncOCO r;
| |
for(int i = 0; i < n; ++i)
| |
{
| |
if(states[i] != ORDER_STATE_PARTIAL) // keep partially filled orders
| |
{
| |
r.remove(tickets[i]) && r.completed();
| |
}
| |
}
| |
}
| |
else
| |
{
| |
// check if open positions exist, place 2 orders if not
| |
if(!trades.select(tickets))
| |
{
| |
MqlTradeRequestSyncOCO r;
| |
SymbolMonitor sm(_Symbol);
| |
| |
const double point = sm.get(SYMBOL_POINT);
| |
const double lot = Volume == 0 ? sm.get(SYMBOL_VOLUME_MIN) : Volume;
| |
const double buy = sm.get(SYMBOL_BID) + point * Distance2SLTP;
| |
const double sell = sm.get(SYMBOL_BID) - point * Distance2SLTP;
| |
| |
r.buyStop(lot, buy, buy - Distance2SLTP * point,
| |
buy + Distance2SLTP * point) && r.completed();
| |
r.sellStop(lot, sell, sell + Distance2SLTP * point,
| |
sell - Distance2SLTP * point) && r.completed();
| |
}
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Tick event handler |
| |
//+------------------------------------------------------------------+
| |
void OnTick()
| |
{
| |
if(FirstTick)
| |
{
| |
RunStrategy();
| |
FirstTick = false;
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| General trade notification handler |
| |
//+------------------------------------------------------------------+
| |
void OnTrade()
| |
{
| |
static ulong count = 0;
| |
PrintFormat("OnTrade(%d)", ++count);
| |
if(ActivationBy == ON_TRADE)
| |
{
| |
RunStrategy();
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Trade transactions handler |
| |
//+------------------------------------------------------------------+
| |
void OnTradeTransaction(const MqlTradeTransaction &transaction,
| |
const MqlTradeRequest &request,
| |
const MqlTradeResult &result)
| |
{
| |
static ulong count = 0;
| |
PrintFormat("OnTradeTransaction(%d)", ++count);
| |
Print(TU::StringOf(transaction));
| |
| |
if(ActivationBy != ON_TRANSACTION) return;
| |
| |
if(transaction.type == TRADE_TRANSACTION_ORDER_DELETE)
| |
{
| |
// when order is deleted from online, it's temporary missing
| |
// both in online list and in the history, so we need to wait for
| |
// the next event TRADE_TRANSACTION_HISTORY_ADD
| |
// NB! In the tester when TRADE_TRANSACTION_ORDER_DELETE is fired
| |
// order is already in the history!
| |
/* // this does not work online:
| |
// m.isReady() == false, because
| |
// neither OrderSelect(), nor HistoryOrderSelect() has effect
| |
OrderMonitor m(transaction.order);
| |
if(m.isReady() && m.get(ORDER_MAGIC) == Magic && m.get(ORDER_SYMBOL) == _Symbol)
| |
{
| |
RunStrategy();
| |
}
| |
*/
| |
}
| |
else if(transaction.type == TRADE_TRANSACTION_HISTORY_ADD)
| |
{
| |
OrderMonitor m(transaction.order);
| |
if(m.isReady() && m.get(ORDER_MAGIC) == Magic && m.get(ORDER_SYMBOL) == _Symbol)
| |
{
| |
// order state is unimportant - in any case we need to remove the remaining one
| |
// if(transaction.order_state == ORDER_STATE_FILLED
| |
// || transaction.order_state == ORDER_STATE_CANCELED ...)
| |
RunStrategy();
| |
}
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Finalization handler |
| |
//+------------------------------------------------------------------+
| |
void OnDeinit(const int r)
| |
{
| |
Print("ExecutionCount = ", ExecutionCount);
| |
}
| |
//+------------------------------------------------------------------+
| |
/*
| |
example output (default settings):
| |
| |
buy stop 0.01 EURUSD at 1.11151 sl: 1.10651 tp: 1.11651 (1.10646 / 1.10683)
| |
sell stop 0.01 EURUSD at 1.10151 sl: 1.10651 tp: 1.09651 (1.10646 / 1.10683)
| |
OnTradeTransaction(1)
| |
TRADE_TRANSACTION_ORDER_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
| |
OnTrade(1)
| |
OnTradeTransaction(2)
| |
TRADE_TRANSACTION_REQUEST
| |
OnTradeTransaction(3)
| |
TRADE_TRANSACTION_ORDER_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, V=0.01
| |
OnTrade(2)
| |
OnTradeTransaction(4)
| |
TRADE_TRANSACTION_REQUEST
| |
order [#3 sell stop 0.01 EURUSD at 1.10151] triggered
| |
deal #2 sell 0.01 EURUSD at 1.10150 done (based on order #3)
| |
deal performed [#2 sell 0.01 EURUSD at 1.10150]
| |
order performed sell 0.01 at 1.10150 [#3 sell stop 0.01 EURUSD at 1.10151]
| |
OnTradeTransaction(5)
| |
TRADE_TRANSACTION_DEAL_ADD, D=2(DEAL_TYPE_SELL), #=3(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10150, SL=1.10651, TP=1.09651, V=0.01, P=3
| |
OnTrade(3)
| |
OnTradeTransaction(6)
| |
TRADE_TRANSACTION_ORDER_DELETE, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, V=0.01, P=3
| |
OnTrade(4)
| |
OnTradeTransaction(7)
| |
TRADE_TRANSACTION_HISTORY_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, P=3
| |
order canceled [#2 buy stop 0.01 EURUSD at 1.11151]
| |
OnTrade(5)
| |
OnTradeTransaction(8)
| |
TRADE_TRANSACTION_ORDER_DELETE, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
| |
OnTrade(6)
| |
OnTradeTransaction(9)
| |
TRADE_TRANSACTION_HISTORY_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01
| |
OnTrade(7)
| |
OnTradeTransaction(10)
| |
TRADE_TRANSACTION_REQUEST
| |
| |
*/
| |
//+------------------------------------------------------------------+
|