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
|
|
|
|
*/
|
|
//+------------------------------------------------------------------+
|