1708 lines
66 KiB
MQL5
1708 lines
66 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Trade.mqh |
|
|
//| Copyright 2000-2025, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#include <Object.mqh>
|
|
#include "OrderInfo.mqh"
|
|
#include "HistoryOrderInfo.mqh"
|
|
#include "PositionInfo.mqh"
|
|
#include "DealInfo.mqh"
|
|
//+------------------------------------------------------------------+
|
|
//| enumerations |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_LOG_LEVELS
|
|
{
|
|
LOG_LEVEL_NO =0,
|
|
LOG_LEVEL_ERRORS=1,
|
|
LOG_LEVEL_ALL =2
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Class CTrade. |
|
|
//| Appointment: Class trade operations. |
|
|
//| Derives from class CObject. |
|
|
//+------------------------------------------------------------------+
|
|
class CTrade : public CObject
|
|
{
|
|
protected:
|
|
MqlTradeRequest m_request; // request data
|
|
MqlTradeResult m_result; // result data
|
|
MqlTradeCheckResult m_check_result; // result check data
|
|
bool m_async_mode; // trade mode
|
|
ulong m_magic; // expert magic number
|
|
ulong m_deviation; // deviation default
|
|
ENUM_ORDER_TYPE_FILLING m_type_filling;
|
|
ENUM_ACCOUNT_MARGIN_MODE m_margin_mode;
|
|
//---
|
|
ENUM_LOG_LEVELS m_log_level;
|
|
|
|
public:
|
|
CTrade(void);
|
|
~CTrade(void);
|
|
//--- methods of access to protected data
|
|
void LogLevel(const ENUM_LOG_LEVELS log_level) { m_log_level=log_level; }
|
|
void Request(MqlTradeRequest &request) const;
|
|
ENUM_TRADE_REQUEST_ACTIONS RequestAction(void) const { return(m_request.action); }
|
|
string RequestActionDescription(void) const;
|
|
ulong RequestMagic(void) const { return(m_request.magic); }
|
|
ulong RequestOrder(void) const { return(m_request.order); }
|
|
ulong RequestPosition(void) const { return(m_request.position); }
|
|
ulong RequestPositionBy(void) const { return(m_request.position_by); }
|
|
string RequestSymbol(void) const { return(m_request.symbol); }
|
|
double RequestVolume(void) const { return(m_request.volume); }
|
|
double RequestPrice(void) const { return(m_request.price); }
|
|
double RequestStopLimit(void) const { return(m_request.stoplimit); }
|
|
double RequestSL(void) const { return(m_request.sl); }
|
|
double RequestTP(void) const { return(m_request.tp); }
|
|
ulong RequestDeviation(void) const { return(m_request.deviation); }
|
|
ENUM_ORDER_TYPE RequestType(void) const { return(m_request.type); }
|
|
string RequestTypeDescription(void) const;
|
|
ENUM_ORDER_TYPE_FILLING RequestTypeFilling(void) const { return(m_request.type_filling); }
|
|
string RequestTypeFillingDescription(void) const;
|
|
ENUM_ORDER_TYPE_TIME RequestTypeTime(void) const { return(m_request.type_time); }
|
|
string RequestTypeTimeDescription(void) const;
|
|
datetime RequestExpiration(void) const { return(m_request.expiration); }
|
|
string RequestComment(void) const { return(m_request.comment); }
|
|
//---
|
|
void Result(MqlTradeResult &result) const;
|
|
uint ResultRetcode(void) const { return(m_result.retcode); }
|
|
string ResultRetcodeDescription(void) const;
|
|
int ResultRetcodeExternal(void) const { return(m_result.retcode_external); }
|
|
ulong ResultDeal(void) const { return(m_result.deal); }
|
|
ulong ResultOrder(void) const { return(m_result.order); }
|
|
double ResultVolume(void) const { return(m_result.volume); }
|
|
double ResultPrice(void) const { return(m_result.price); }
|
|
double ResultBid(void) const { return(m_result.bid); }
|
|
double ResultAsk(void) const { return(m_result.ask); }
|
|
string ResultComment(void) const { return(m_result.comment); }
|
|
//---
|
|
void CheckResult(MqlTradeCheckResult &check_result) const;
|
|
uint CheckResultRetcode(void) const { return(m_check_result.retcode); }
|
|
string CheckResultRetcodeDescription(void) const;
|
|
double CheckResultBalance(void) const { return(m_check_result.balance); }
|
|
double CheckResultEquity(void) const { return(m_check_result.equity); }
|
|
double CheckResultProfit(void) const { return(m_check_result.profit); }
|
|
double CheckResultMargin(void) const { return(m_check_result.margin); }
|
|
double CheckResultMarginFree(void) const { return(m_check_result.margin_free); }
|
|
double CheckResultMarginLevel(void) const { return(m_check_result.margin_level); }
|
|
string CheckResultComment(void) const { return(m_check_result.comment); }
|
|
//--- trade methods
|
|
void SetAsyncMode(const bool mode) { m_async_mode=mode; }
|
|
void SetExpertMagicNumber(const ulong magic) { m_magic=magic; }
|
|
void SetDeviationInPoints(const ulong deviation) { m_deviation=deviation; }
|
|
void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING filling) { m_type_filling=filling; }
|
|
bool SetTypeFillingBySymbol(const string symbol);
|
|
void SetMarginMode(void) { m_margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); }
|
|
//--- methods for working with positions
|
|
bool PositionOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,
|
|
const double price,const double sl,const double tp,const string comment="");
|
|
bool PositionModify(const string symbol,const double sl,const double tp);
|
|
bool PositionModify(const ulong ticket,const double sl,const double tp);
|
|
bool PositionClose(const string symbol,const ulong deviation=ULONG_MAX);
|
|
bool PositionClose(const ulong ticket,const ulong deviation=ULONG_MAX);
|
|
bool PositionCloseBy(const ulong ticket,const ulong ticket_by);
|
|
bool PositionClosePartial(const string symbol,const double volume,const ulong deviation=ULONG_MAX);
|
|
bool PositionClosePartial(const ulong ticket,const double volume,const ulong deviation=ULONG_MAX);
|
|
//--- methods for working with pending orders
|
|
bool OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,
|
|
const double limit_price,const double price,const double sl,const double tp,
|
|
ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,
|
|
const string comment="");
|
|
bool OrderModify(const ulong ticket,const double price,const double sl,const double tp,
|
|
const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit=0.0);
|
|
bool OrderDelete(const ulong ticket);
|
|
//--- additions methods
|
|
bool Buy(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment="");
|
|
bool Sell(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment="");
|
|
bool BuyLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="");
|
|
bool BuyStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="");
|
|
bool SellLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="");
|
|
bool SellStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="");
|
|
//--- method check
|
|
virtual double CheckVolume(const string symbol,double volume,double price,ENUM_ORDER_TYPE order_type);
|
|
virtual bool OrderCheck(const MqlTradeRequest &request,MqlTradeCheckResult &check_result);
|
|
virtual bool OrderSend(const MqlTradeRequest &request,MqlTradeResult &result);
|
|
//--- info methods
|
|
void PrintRequest(void) const;
|
|
void PrintResult(void) const;
|
|
//--- positions
|
|
string FormatPositionType(string &str,const uint type) const;
|
|
//--- orders
|
|
string FormatOrderType(string &str,const uint type) const;
|
|
string FormatOrderStatus(string &str,const uint status) const;
|
|
string FormatOrderTypeFilling(string &str,const uint type) const;
|
|
string FormatOrderTypeTime(string &str,const uint type) const;
|
|
string FormatOrderPrice(string &str,const double price_order,const double price_trigger,const uint digits) const;
|
|
//--- trade request
|
|
string FormatRequest(string &str,const MqlTradeRequest &request) const;
|
|
string FormatRequestResult(string &str,const MqlTradeRequest &request,const MqlTradeResult &result) const;
|
|
|
|
protected:
|
|
bool FillingCheck(const string symbol);
|
|
bool ExpirationCheck(const string symbol);
|
|
bool OrderTypeCheck(const string symbol);
|
|
void ClearStructures(void);
|
|
bool IsStopped(const string function);
|
|
bool IsHedging(void) const { return(m_margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); }
|
|
//--- position select depending on netting or hedging
|
|
bool SelectPosition(const string symbol);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CTrade::CTrade(void) : m_async_mode(false),
|
|
m_magic(0),
|
|
m_deviation(10),
|
|
m_type_filling(ORDER_FILLING_FOK),
|
|
m_log_level(LOG_LEVEL_ERRORS)
|
|
{
|
|
SetMarginMode();
|
|
//--- initialize protected data
|
|
ClearStructures();
|
|
//--- check programm mode
|
|
if(MQLInfoInteger(ENUM_MQL_INFO_INTEGER::MQL_TESTER))
|
|
m_log_level=LOG_LEVEL_ALL;
|
|
if(MQLInfoInteger(ENUM_MQL_INFO_INTEGER::MQL_OPTIMIZATION))
|
|
m_log_level=LOG_LEVEL_NO;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CTrade::~CTrade(void)
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the request structure |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::Request(MqlTradeRequest &request) const
|
|
{
|
|
request.action =m_request.action;
|
|
request.magic =m_request.magic;
|
|
request.order =m_request.order;
|
|
request.symbol =m_request.symbol;
|
|
request.volume =m_request.volume;
|
|
request.price =m_request.price;
|
|
request.stoplimit =m_request.stoplimit;
|
|
request.sl =m_request.sl;
|
|
request.tp =m_request.tp;
|
|
request.deviation =m_request.deviation;
|
|
request.type =m_request.type;
|
|
request.type_filling=m_request.type_filling;
|
|
request.type_time =m_request.type_time;
|
|
request.expiration =m_request.expiration;
|
|
request.comment =m_request.comment;
|
|
request.position =m_request.position;
|
|
request.position_by =m_request.position_by;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the trade action as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::RequestActionDescription(void) const
|
|
{
|
|
string str;
|
|
//---
|
|
FormatRequest(str,m_request);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the order type as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::RequestTypeDescription(void) const
|
|
{
|
|
string str;
|
|
//---
|
|
FormatOrderType(str,(uint)m_request.order);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the order type filling as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::RequestTypeFillingDescription(void) const
|
|
{
|
|
string str;
|
|
//---
|
|
FormatOrderTypeFilling(str,m_request.type_filling);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the order type time as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::RequestTypeTimeDescription(void) const
|
|
{
|
|
string str;
|
|
//---
|
|
FormatOrderTypeTime(str,m_request.type_time);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the result structure |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::Result(MqlTradeResult &result) const
|
|
{
|
|
result.retcode =m_result.retcode;
|
|
result.deal =m_result.deal;
|
|
result.order =m_result.order;
|
|
result.volume =m_result.volume;
|
|
result.price =m_result.price;
|
|
result.bid =m_result.bid;
|
|
result.ask =m_result.ask;
|
|
result.comment =m_result.comment;
|
|
result.request_id=m_result.request_id;
|
|
result.retcode_external=m_result.retcode_external;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the retcode value as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::ResultRetcodeDescription(void) const
|
|
{
|
|
string str;
|
|
//---
|
|
FormatRequestResult(str,m_request,m_result);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the check result structure |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::CheckResult(MqlTradeCheckResult &check_result) const
|
|
{
|
|
//--- copy structure
|
|
check_result.retcode =m_check_result.retcode;
|
|
check_result.balance =m_check_result.balance;
|
|
check_result.equity =m_check_result.equity;
|
|
check_result.profit =m_check_result.profit;
|
|
check_result.margin =m_check_result.margin;
|
|
check_result.margin_free =m_check_result.margin_free;
|
|
check_result.margin_level=m_check_result.margin_level;
|
|
check_result.comment =m_check_result.comment;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the check retcode value as string |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::CheckResultRetcodeDescription(void) const
|
|
{
|
|
string str;
|
|
MqlTradeResult result;
|
|
//---
|
|
result.retcode=m_check_result.retcode;
|
|
FormatRequestResult(str,m_request,result);
|
|
//---
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Open position |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,
|
|
const double price,const double sl,const double tp,const string comment)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check
|
|
if(order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID;
|
|
m_result.comment="Invalid order type";
|
|
return(false);
|
|
}
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_DEAL;
|
|
m_request.symbol =symbol;
|
|
m_request.magic =m_magic;
|
|
m_request.volume =volume;
|
|
m_request.type =order_type;
|
|
m_request.price =price;
|
|
m_request.sl =sl;
|
|
m_request.tp =tp;
|
|
m_request.deviation=m_deviation;
|
|
//--- check order type
|
|
if(!OrderTypeCheck(symbol))
|
|
return(false);
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
m_request.comment=comment;
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modify specified opened position |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionModify(const string symbol,const double sl,const double tp)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- check position existence
|
|
if(!SelectPosition(symbol))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_SLTP;
|
|
m_request.symbol =symbol;
|
|
m_request.magic =m_magic;
|
|
m_request.sl =sl;
|
|
m_request.tp =tp;
|
|
m_request.position=PositionGetInteger(POSITION_TICKET);
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modify specified opened position |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- check position existence
|
|
if(!PositionSelectByTicket(ticket))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_SLTP;
|
|
m_request.position=ticket;
|
|
m_request.symbol =PositionGetString(POSITION_SYMBOL);
|
|
m_request.magic =m_magic;
|
|
m_request.sl =sl;
|
|
m_request.tp =tp;
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Close specified opened position |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionClose(const string symbol,const ulong deviation)
|
|
{
|
|
bool partial_close=false;
|
|
int retry_count =10;
|
|
uint retcode =TRADE_RETCODE_REJECT;
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
do
|
|
{
|
|
//--- check
|
|
if(SelectPosition(symbol))
|
|
{
|
|
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
|
|
{
|
|
//--- prepare request for close BUY position
|
|
m_request.type =ORDER_TYPE_SELL;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID);
|
|
}
|
|
else
|
|
{
|
|
//--- prepare request for close SELL position
|
|
m_request.type =ORDER_TYPE_BUY;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- position not found
|
|
m_result.retcode=retcode;
|
|
return(false);
|
|
}
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_DEAL;
|
|
m_request.symbol =symbol;
|
|
m_request.volume =PositionGetDouble(POSITION_VOLUME);
|
|
m_request.magic =m_magic;
|
|
m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation;
|
|
m_request.position =PositionGetInteger(POSITION_TICKET);
|
|
//--- check volume
|
|
double max_volume=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
|
|
if(m_request.volume>max_volume)
|
|
{
|
|
m_request.volume=max_volume;
|
|
partial_close=true;
|
|
}
|
|
else
|
|
partial_close=false;
|
|
//--- hedging? just send order
|
|
if(IsHedging())
|
|
return(OrderSend(m_request,m_result));
|
|
//--- order send
|
|
if(!OrderSend(m_request,m_result))
|
|
{
|
|
if(--retry_count!=0)
|
|
continue;
|
|
if(retcode==TRADE_RETCODE_DONE_PARTIAL)
|
|
m_result.retcode=retcode;
|
|
return(false);
|
|
}
|
|
//--- WARNING. If position volume exceeds the maximum volume allowed for deal,
|
|
//--- and when the asynchronous trade mode is on, for safety reasons, position is closed not completely,
|
|
//--- but partially. It is decreased by the maximum volume allowed for deal.
|
|
if(m_async_mode)
|
|
break;
|
|
retcode=TRADE_RETCODE_DONE_PARTIAL;
|
|
if(partial_close)
|
|
Sleep(1000);
|
|
}
|
|
while(partial_close);
|
|
//--- succeed
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Close specified opened position |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionClose(const ulong ticket,const ulong deviation)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- check position existence
|
|
if(!PositionSelectByTicket(ticket))
|
|
return(false);
|
|
string symbol=PositionGetString(POSITION_SYMBOL);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
//--- check
|
|
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
|
|
{
|
|
//--- prepare request for close BUY position
|
|
m_request.type =ORDER_TYPE_SELL;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID);
|
|
}
|
|
else
|
|
{
|
|
//--- prepare request for close SELL position
|
|
m_request.type =ORDER_TYPE_BUY;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK);
|
|
}
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_DEAL;
|
|
m_request.position =ticket;
|
|
m_request.symbol =symbol;
|
|
m_request.volume =PositionGetDouble(POSITION_VOLUME);
|
|
m_request.magic =m_magic;
|
|
m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation;
|
|
//--- close position
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Close one position by other |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionCloseBy(const ulong ticket,const ulong ticket_by)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- check hedging mode
|
|
if(!IsHedging())
|
|
return(false);
|
|
//--- check position existence
|
|
if(!PositionSelectByTicket(ticket))
|
|
return(false);
|
|
string symbol=PositionGetString(POSITION_SYMBOL);
|
|
ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
if(!PositionSelectByTicket(ticket_by))
|
|
return(false);
|
|
string symbol_by=PositionGetString(POSITION_SYMBOL);
|
|
ENUM_POSITION_TYPE type_by=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
//--- check positions
|
|
if(type==type_by)
|
|
return(false);
|
|
if(symbol!=symbol_by)
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_CLOSE_BY;
|
|
m_request.position =ticket;
|
|
m_request.position_by=ticket_by;
|
|
m_request.magic =m_magic;
|
|
//--- close position
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Partial close specified opened position (for hedging mode only) |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionClosePartial(const string symbol,const double volume,const ulong deviation)
|
|
{
|
|
uint retcode=TRADE_RETCODE_REJECT;
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- for hedging mode only
|
|
if(!IsHedging())
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
//--- check
|
|
if(SelectPosition(symbol))
|
|
{
|
|
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
|
|
{
|
|
//--- prepare request for close BUY position
|
|
m_request.type =ORDER_TYPE_SELL;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID);
|
|
}
|
|
else
|
|
{
|
|
//--- prepare request for close SELL position
|
|
m_request.type =ORDER_TYPE_BUY;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- position not found
|
|
m_result.retcode=retcode;
|
|
return(false);
|
|
}
|
|
//--- check volume
|
|
double position_volume=PositionGetDouble(POSITION_VOLUME);
|
|
if(position_volume>volume)
|
|
position_volume=volume;
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_DEAL;
|
|
m_request.symbol =symbol;
|
|
m_request.volume =position_volume;
|
|
m_request.magic =m_magic;
|
|
m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation;
|
|
m_request.position =PositionGetInteger(POSITION_TICKET);
|
|
//--- hedging? just send order
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Partial close specified opened position (for hedging mode only) |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::PositionClosePartial(const ulong ticket,const double volume,const ulong deviation)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- for hedging mode only
|
|
if(!IsHedging())
|
|
return(false);
|
|
//--- check position existence
|
|
if(!PositionSelectByTicket(ticket))
|
|
return(false);
|
|
string symbol=PositionGetString(POSITION_SYMBOL);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
//--- check
|
|
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
|
|
{
|
|
//--- prepare request for close BUY position
|
|
m_request.type =ORDER_TYPE_SELL;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID);
|
|
}
|
|
else
|
|
{
|
|
//--- prepare request for close SELL position
|
|
m_request.type =ORDER_TYPE_BUY;
|
|
m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK);
|
|
}
|
|
//--- check volume
|
|
double position_volume=PositionGetDouble(POSITION_VOLUME);
|
|
if(position_volume>volume)
|
|
position_volume=volume;
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_DEAL;
|
|
m_request.position =ticket;
|
|
m_request.symbol =symbol;
|
|
m_request.volume =position_volume;
|
|
m_request.magic =m_magic;
|
|
m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation;
|
|
//--- close position
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Installation pending order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,const double limit_price,
|
|
const double price,const double sl,const double tp,
|
|
ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const string comment)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
return(false);
|
|
//--- check order type
|
|
if(order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_SELL)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID;
|
|
m_result.comment="Invalid order type";
|
|
return(false);
|
|
}
|
|
//--- check order expiration
|
|
if(type_time==ORDER_TIME_GTC && expiration==0)
|
|
{
|
|
int exp=(int)SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_MODE);
|
|
if((exp&SYMBOL_EXPIRATION_GTC)!=SYMBOL_EXPIRATION_GTC)
|
|
{
|
|
//--- if you place order for an unlimited time and if placing of such orders is prohibited
|
|
//--- try to place order with expiration at the end of the day
|
|
if((exp&SYMBOL_EXPIRATION_DAY)!=SYMBOL_EXPIRATION_DAY)
|
|
{
|
|
//--- if even this is not possible - error
|
|
Print(__FUNCTION__,": Error: Unable to place order without explicitly specified expiration time");
|
|
m_result.retcode=TRADE_RETCODE_INVALID_EXPIRATION;
|
|
m_result.comment="Invalid expiration type";
|
|
return(false);
|
|
}
|
|
type_time=ORDER_TIME_DAY;
|
|
}
|
|
}
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_PENDING;
|
|
m_request.symbol =symbol;
|
|
m_request.magic =m_magic;
|
|
m_request.volume =volume;
|
|
m_request.type =order_type;
|
|
m_request.stoplimit =limit_price;
|
|
m_request.price =price;
|
|
m_request.sl =sl;
|
|
m_request.tp =tp;
|
|
m_request.type_time =type_time;
|
|
m_request.expiration =expiration;
|
|
//--- check order type
|
|
if(!OrderTypeCheck(symbol))
|
|
return(false);
|
|
//--- check filling
|
|
if(!FillingCheck(symbol))
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_FILL;
|
|
Print(__FUNCTION__+": Invalid filling type");
|
|
return(false);
|
|
}
|
|
//--- check expiration
|
|
if(!ExpirationCheck(symbol))
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_EXPIRATION;
|
|
Print(__FUNCTION__+": Invalid expiration type");
|
|
return(false);
|
|
}
|
|
m_request.comment=comment;
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Modify specified pending order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderModify(const ulong ticket,const double price,const double sl,const double tp,
|
|
const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- check order existence
|
|
if(!OrderSelect(ticket))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- setting request
|
|
m_request.symbol =OrderGetString(ORDER_SYMBOL);
|
|
m_request.action =TRADE_ACTION_MODIFY;
|
|
m_request.magic =m_magic;
|
|
m_request.order =ticket;
|
|
m_request.price =price;
|
|
m_request.stoplimit =stoplimit;
|
|
m_request.sl =sl;
|
|
m_request.tp =tp;
|
|
m_request.type_time =type_time;
|
|
m_request.expiration =expiration;
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Delete specified pending order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderDelete(const ulong ticket)
|
|
{
|
|
//--- check stopped
|
|
if(IsStopped(__FUNCTION__))
|
|
return(false);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- setting request
|
|
m_request.action =TRADE_ACTION_REMOVE;
|
|
m_request.magic =m_magic;
|
|
m_request.order =ticket;
|
|
//--- action and return the result
|
|
return(OrderSend(m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Output full information of request to log |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::PrintRequest(void) const
|
|
{
|
|
if(m_log_level<LOG_LEVEL_ALL)
|
|
return;
|
|
//---
|
|
string str;
|
|
PrintFormat("%s",FormatRequest(str,m_request));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Output full information of result to log |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::PrintResult(void) const
|
|
{
|
|
if(m_log_level<LOG_LEVEL_ALL)
|
|
return;
|
|
//---
|
|
string str;
|
|
PrintFormat("%s",FormatRequestResult(str,m_request,m_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Clear structures m_request,m_result and m_check_result |
|
|
//+------------------------------------------------------------------+
|
|
void CTrade::ClearStructures(void)
|
|
{
|
|
ZeroMemory(m_request);
|
|
ZeroMemory(m_result);
|
|
ZeroMemory(m_check_result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks forced shutdown of MQL5-program |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::IsStopped(const string function)
|
|
{
|
|
if(!::IsStopped())
|
|
return(false);
|
|
//--- MQL5 program is stopped
|
|
PrintFormat("%s: MQL5 program is stopped. Trading is disabled",function);
|
|
m_result.retcode=TRADE_RETCODE_CLIENT_DISABLES_AT;
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buy operation |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::Buy(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment="")
|
|
{
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
string symbol_name=(symbol==NULL) ? _Symbol : symbol;
|
|
//--- check price
|
|
if(price==0.0)
|
|
price=SymbolInfoDouble(symbol_name,SYMBOL_ASK);
|
|
//---
|
|
return(PositionOpen(symbol_name,ORDER_TYPE_BUY,volume,price,sl,tp,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sell operation |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::Sell(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment="")
|
|
{
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
string symbol_name=(symbol==NULL) ? _Symbol : symbol;
|
|
//--- check price
|
|
if(price==0.0)
|
|
price=SymbolInfoDouble(symbol_name,SYMBOL_BID);
|
|
//---
|
|
return(PositionOpen(symbol_name,ORDER_TYPE_SELL,volume,price,sl,tp,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send BUY_LIMIT order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::BuyLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="")
|
|
{
|
|
string symbol_name;
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
symbol_name=(symbol==NULL)?Symbol():symbol;
|
|
//--- send "BUY_LIMIT" order
|
|
return(OrderOpen(symbol_name,ORDER_TYPE_BUY_LIMIT,volume,0.0,price,sl,tp,type_time,expiration,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send BUY_STOP order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::BuyStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="")
|
|
{
|
|
string symbol_name;
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
symbol_name=(symbol==NULL)?Symbol():symbol;
|
|
//--- send "BUY_STOP" order
|
|
return(OrderOpen(symbol_name,ORDER_TYPE_BUY_STOP,volume,0.0,price,sl,tp,type_time,expiration,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send SELL_LIMIT order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::SellLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="")
|
|
{
|
|
string symbol_name;
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
symbol_name=(symbol==NULL)?Symbol():symbol;
|
|
//--- send "SELL_LIMIT" order
|
|
return(OrderOpen(symbol_name,ORDER_TYPE_SELL_LIMIT,volume,0.0,price,sl,tp,type_time,expiration,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send SELL_STOP order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::SellStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0,
|
|
const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment="")
|
|
{
|
|
string symbol_name;
|
|
//--- check volume
|
|
if(volume<=0.0)
|
|
{
|
|
m_result.retcode=TRADE_RETCODE_INVALID_VOLUME;
|
|
return(false);
|
|
}
|
|
//--- check symbol
|
|
symbol_name=(symbol==NULL)?Symbol():symbol;
|
|
//--- send "SELL_STOP" order
|
|
return(OrderOpen(symbol_name,ORDER_TYPE_SELL_STOP,volume,0.0,price,sl,tp,type_time,expiration,comment));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the position type to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatPositionType(string &str,const uint type) const
|
|
{
|
|
//--- see the type
|
|
switch(type)
|
|
{
|
|
case POSITION_TYPE_BUY:
|
|
str="buy";
|
|
break;
|
|
case POSITION_TYPE_SELL:
|
|
str="sell";
|
|
break;
|
|
default:
|
|
str="unknown position type "+(string)type;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the order type to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatOrderType(string &str,const uint type) const
|
|
{
|
|
//--- see the type
|
|
switch(type)
|
|
{
|
|
case ORDER_TYPE_BUY:
|
|
str="buy";
|
|
break;
|
|
case ORDER_TYPE_SELL:
|
|
str="sell";
|
|
break;
|
|
case ORDER_TYPE_BUY_LIMIT:
|
|
str="buy limit";
|
|
break;
|
|
case ORDER_TYPE_SELL_LIMIT:
|
|
str="sell limit";
|
|
break;
|
|
case ORDER_TYPE_BUY_STOP:
|
|
str="buy stop";
|
|
break;
|
|
case ORDER_TYPE_SELL_STOP:
|
|
str="sell stop";
|
|
break;
|
|
case ORDER_TYPE_BUY_STOP_LIMIT:
|
|
str="buy stop limit";
|
|
break;
|
|
case ORDER_TYPE_SELL_STOP_LIMIT:
|
|
str="sell stop limit";
|
|
break;
|
|
default:
|
|
str="unknown order type "+(string)type;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the order filling type to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatOrderTypeFilling(string &str,const uint type) const
|
|
{
|
|
//--- see the type
|
|
switch(type)
|
|
{
|
|
case ORDER_FILLING_RETURN:
|
|
str="return remainder";
|
|
break;
|
|
case ORDER_FILLING_IOC:
|
|
str="cancel remainder";
|
|
break;
|
|
case ORDER_FILLING_FOK:
|
|
str="fill or kill";
|
|
break;
|
|
default:
|
|
str="unknown type filling "+(string)type;
|
|
break;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the type of order by expiration to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatOrderTypeTime(string &str,const uint type) const
|
|
{
|
|
//--- see the type
|
|
switch(type)
|
|
{
|
|
case ORDER_TIME_GTC:
|
|
str="gtc";
|
|
break;
|
|
case ORDER_TIME_DAY:
|
|
str="day";
|
|
break;
|
|
case ORDER_TIME_SPECIFIED:
|
|
str="specified";
|
|
break;
|
|
case ORDER_TIME_SPECIFIED_DAY:
|
|
str="specified day";
|
|
break;
|
|
default:
|
|
str="unknown type time "+(string)type;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the order prices to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatOrderPrice(string &str,const double price_order,const double price_trigger,const uint digits) const
|
|
{
|
|
string price,trigger;
|
|
//--- Is there its trigger price?
|
|
if(price_trigger)
|
|
{
|
|
price =DoubleToString(price_order,digits);
|
|
trigger=DoubleToString(price_trigger,digits);
|
|
str =StringFormat("%s (%s)",price,trigger);
|
|
}
|
|
else
|
|
str=DoubleToString(price_order,digits);
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the parameters of a trade request to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatRequest(string &str,const MqlTradeRequest &request) const
|
|
{
|
|
string type,price,price_new;
|
|
string tmp_string;
|
|
long tmp_long;
|
|
//--- clean
|
|
str="";
|
|
//--- set up
|
|
string symbol_name=(request.symbol==NULL) ? _Symbol : request.symbol;
|
|
int digits=_Digits;
|
|
ENUM_SYMBOL_TRADE_EXECUTION trade_execution=0;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long))
|
|
digits=(int)tmp_long;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_TRADE_EXEMODE,tmp_long))
|
|
trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp_long;
|
|
//--- see what is wanted
|
|
switch(request.action)
|
|
{
|
|
//--- instant execution of a deal
|
|
case TRADE_ACTION_DEAL:
|
|
switch(trade_execution)
|
|
{
|
|
//--- request execution
|
|
case SYMBOL_TRADE_EXECUTION_REQUEST:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("request %s %s position #%I64u %s at %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.position,
|
|
request.symbol,
|
|
DoubleToString(request.price,digits));
|
|
else
|
|
str=StringFormat("request %s %s %s at %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.symbol,
|
|
DoubleToString(request.price,digits));
|
|
//--- Is there SL or TP?
|
|
if(request.sl!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" sl: %s",DoubleToString(request.sl,digits));
|
|
str+=tmp_string;
|
|
}
|
|
if(request.tp!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" tp: %s",DoubleToString(request.tp,digits));
|
|
str+=tmp_string;
|
|
}
|
|
break;
|
|
//--- instant execution
|
|
case SYMBOL_TRADE_EXECUTION_INSTANT:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("instant %s %s position #%I64u %s at %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.position,
|
|
request.symbol,
|
|
DoubleToString(request.price,digits));
|
|
else
|
|
str=StringFormat("instant %s %s %s at %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.symbol,
|
|
DoubleToString(request.price,digits));
|
|
//--- Is there SL or TP?
|
|
if(request.sl!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" sl: %s",DoubleToString(request.sl,digits));
|
|
str+=tmp_string;
|
|
}
|
|
if(request.tp!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" tp: %s",DoubleToString(request.tp,digits));
|
|
str+=tmp_string;
|
|
}
|
|
break;
|
|
//--- market execution
|
|
case SYMBOL_TRADE_EXECUTION_MARKET:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("market %s %s position #%I64u %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.position,
|
|
request.symbol);
|
|
else
|
|
str=StringFormat("market %s %s %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.symbol);
|
|
//--- Is there SL or TP?
|
|
if(request.sl!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" sl: %s",DoubleToString(request.sl,digits));
|
|
str+=tmp_string;
|
|
}
|
|
if(request.tp!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" tp: %s",DoubleToString(request.tp,digits));
|
|
str+=tmp_string;
|
|
}
|
|
break;
|
|
//--- exchange execution
|
|
case SYMBOL_TRADE_EXECUTION_EXCHANGE:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("exchange %s %s position #%I64u %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.position,
|
|
request.symbol);
|
|
else
|
|
str=StringFormat("exchange %s %s %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.symbol);
|
|
//--- Is there SL or TP?
|
|
if(request.sl!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" sl: %s",DoubleToString(request.sl,digits));
|
|
str+=tmp_string;
|
|
}
|
|
if(request.tp!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" tp: %s",DoubleToString(request.tp,digits));
|
|
str+=tmp_string;
|
|
}
|
|
break;
|
|
}
|
|
//--- end of TRADE_ACTION_DEAL processing
|
|
break;
|
|
|
|
//--- setting a pending order
|
|
case TRADE_ACTION_PENDING:
|
|
str=StringFormat("%s %s %s at %s",
|
|
FormatOrderType(type,request.type),
|
|
DoubleToString(request.volume,2),
|
|
request.symbol,
|
|
FormatOrderPrice(price,request.price,request.stoplimit,digits));
|
|
//--- Is there SL or TP?
|
|
if(request.sl!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" sl: %s",DoubleToString(request.sl,digits));
|
|
str+=tmp_string;
|
|
}
|
|
if(request.tp!=0.0)
|
|
{
|
|
tmp_string=StringFormat(" tp: %s",DoubleToString(request.tp,digits));
|
|
str+=tmp_string;
|
|
}
|
|
break;
|
|
|
|
//--- Setting SL/TP
|
|
case TRADE_ACTION_SLTP:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("modify position #%I64u %s (sl: %s, tp: %s)",
|
|
request.position,
|
|
request.symbol,
|
|
DoubleToString(request.sl,digits),
|
|
DoubleToString(request.tp,digits));
|
|
else
|
|
str=StringFormat("modify %s (sl: %s, tp: %s)",
|
|
request.symbol,
|
|
DoubleToString(request.sl,digits),
|
|
DoubleToString(request.tp,digits));
|
|
break;
|
|
|
|
//--- modifying a pending order
|
|
case TRADE_ACTION_MODIFY:
|
|
str=StringFormat("modify #%I64u at %s (sl: %s tp: %s)",
|
|
request.order,
|
|
FormatOrderPrice(price_new,request.price,request.stoplimit,digits),
|
|
DoubleToString(request.sl,digits),
|
|
DoubleToString(request.tp,digits));
|
|
break;
|
|
|
|
//--- deleting a pending order
|
|
case TRADE_ACTION_REMOVE:
|
|
str=StringFormat("cancel #%I64u",request.order);
|
|
break;
|
|
|
|
//--- close by
|
|
case TRADE_ACTION_CLOSE_BY:
|
|
if(IsHedging() && request.position!=0)
|
|
str=StringFormat("close position #%I64u by #%I64u",request.position,request.position_by);
|
|
else
|
|
str=StringFormat("wrong action close by (#%I64u by #%I64u)",request.position,request.position_by);
|
|
break;
|
|
|
|
default:
|
|
str="unknown action "+(string)request.action;
|
|
break;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Converts the result of a request to text |
|
|
//+------------------------------------------------------------------+
|
|
string CTrade::FormatRequestResult(string &str,const MqlTradeRequest &request,const MqlTradeResult &result) const
|
|
{
|
|
//--- set up
|
|
string symbol_name=(request.symbol==NULL) ? _Symbol : request.symbol;
|
|
int digits=_Digits;
|
|
long tmp_long;
|
|
ENUM_SYMBOL_TRADE_EXECUTION trade_execution=0;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long))
|
|
digits=(int)tmp_long;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_TRADE_EXEMODE,tmp_long))
|
|
trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp_long;
|
|
//--- see the response code
|
|
switch(result.retcode)
|
|
{
|
|
case TRADE_RETCODE_REQUOTE:
|
|
str=StringFormat("requote (%s/%s)",
|
|
DoubleToString(result.bid,digits),
|
|
DoubleToString(result.ask,digits));
|
|
break;
|
|
|
|
case TRADE_RETCODE_DONE:
|
|
if(request.action==TRADE_ACTION_DEAL &&
|
|
(trade_execution==SYMBOL_TRADE_EXECUTION_REQUEST ||
|
|
trade_execution==SYMBOL_TRADE_EXECUTION_INSTANT ||
|
|
trade_execution==SYMBOL_TRADE_EXECUTION_MARKET))
|
|
str=StringFormat("done at %s",DoubleToString(result.price,digits));
|
|
else
|
|
str="done";
|
|
break;
|
|
|
|
case TRADE_RETCODE_DONE_PARTIAL:
|
|
if(request.action==TRADE_ACTION_DEAL &&
|
|
(trade_execution==SYMBOL_TRADE_EXECUTION_REQUEST ||
|
|
trade_execution==SYMBOL_TRADE_EXECUTION_INSTANT ||
|
|
trade_execution==SYMBOL_TRADE_EXECUTION_MARKET))
|
|
str=StringFormat("done partially %s at %s",
|
|
DoubleToString(result.volume,2),
|
|
DoubleToString(result.price,digits));
|
|
else
|
|
str=StringFormat("done partially %s",
|
|
DoubleToString(result.volume,2));
|
|
break;
|
|
|
|
case TRADE_RETCODE_REJECT:
|
|
str="rejected";
|
|
break;
|
|
case TRADE_RETCODE_CANCEL:
|
|
str="canceled";
|
|
break;
|
|
case TRADE_RETCODE_PLACED:
|
|
str="placed";
|
|
break;
|
|
case TRADE_RETCODE_ERROR:
|
|
str="common error";
|
|
break;
|
|
case TRADE_RETCODE_TIMEOUT:
|
|
str="timeout";
|
|
break;
|
|
case TRADE_RETCODE_INVALID:
|
|
str="invalid request";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_VOLUME:
|
|
str="invalid volume";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_PRICE:
|
|
str="invalid price";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_STOPS:
|
|
str="invalid stops";
|
|
break;
|
|
case TRADE_RETCODE_TRADE_DISABLED:
|
|
str="trade disabled";
|
|
break;
|
|
case TRADE_RETCODE_MARKET_CLOSED:
|
|
str="market closed";
|
|
break;
|
|
case TRADE_RETCODE_NO_MONEY:
|
|
str="not enough money";
|
|
break;
|
|
case TRADE_RETCODE_PRICE_CHANGED:
|
|
str="price changed";
|
|
break;
|
|
case TRADE_RETCODE_PRICE_OFF:
|
|
str="off quotes";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_EXPIRATION:
|
|
str="invalid expiration";
|
|
break;
|
|
case TRADE_RETCODE_ORDER_CHANGED:
|
|
str="order changed";
|
|
break;
|
|
case TRADE_RETCODE_TOO_MANY_REQUESTS:
|
|
str="too many requests";
|
|
break;
|
|
case TRADE_RETCODE_NO_CHANGES:
|
|
str="no changes";
|
|
break;
|
|
case TRADE_RETCODE_SERVER_DISABLES_AT:
|
|
str="auto trading disabled by server";
|
|
break;
|
|
case TRADE_RETCODE_CLIENT_DISABLES_AT:
|
|
str="auto trading disabled by client";
|
|
break;
|
|
case TRADE_RETCODE_LOCKED:
|
|
str="locked";
|
|
break;
|
|
case TRADE_RETCODE_FROZEN:
|
|
str="frozen";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_FILL:
|
|
str="invalid fill";
|
|
break;
|
|
case TRADE_RETCODE_CONNECTION:
|
|
str="no connection";
|
|
break;
|
|
case TRADE_RETCODE_ONLY_REAL:
|
|
str="only real";
|
|
break;
|
|
case TRADE_RETCODE_LIMIT_ORDERS:
|
|
str="limit orders";
|
|
break;
|
|
case TRADE_RETCODE_LIMIT_VOLUME:
|
|
str="limit volume";
|
|
break;
|
|
case TRADE_RETCODE_POSITION_CLOSED:
|
|
str="position closed";
|
|
break;
|
|
case TRADE_RETCODE_INVALID_ORDER:
|
|
str="invalid order";
|
|
break;
|
|
case TRADE_RETCODE_CLOSE_ORDER_EXIST:
|
|
str="close order already exists";
|
|
break;
|
|
case TRADE_RETCODE_LIMIT_POSITIONS:
|
|
str="limit positions";
|
|
break;
|
|
default:
|
|
str="unknown retcode "+(string)result.retcode;
|
|
}
|
|
//--- return the result
|
|
return(str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double CTrade::CheckVolume(const string symbol,double volume,double price,ENUM_ORDER_TYPE order_type)
|
|
{
|
|
//--- check
|
|
if(order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL)
|
|
return(0.0);
|
|
double free_margin=AccountInfoDouble(ENUM_ACCOUNT_INFO_DOUBLE::ACCOUNT_MARGIN_FREE);
|
|
if(free_margin<=0.0)
|
|
return(0.0);
|
|
//--- clean
|
|
ClearStructures();
|
|
//--- setting request
|
|
m_request.action=TRADE_ACTION_DEAL;
|
|
m_request.symbol=symbol;
|
|
m_request.volume=volume;
|
|
m_request.type =order_type;
|
|
m_request.price =price;
|
|
//--- action and return the result
|
|
if(!::OrderCheck(m_request,m_check_result) && m_check_result.margin_free<0.0)
|
|
{
|
|
double coeff=free_margin/(free_margin-m_check_result.margin_free);
|
|
double lots=NormalizeDouble(volume*coeff,2);
|
|
if(lots<volume)
|
|
{
|
|
//--- normalize and check limits
|
|
double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
|
|
if(stepvol>0.0)
|
|
volume=stepvol*(MathFloor(lots/stepvol)-1);
|
|
//---
|
|
double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
|
|
if(volume<minvol)
|
|
volume=0.0;
|
|
}
|
|
}
|
|
return(volume);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks if the m_request structure is filled correctly |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderCheck(const MqlTradeRequest &request,MqlTradeCheckResult &check_result)
|
|
{
|
|
//--- action and return the result
|
|
return(::OrderCheck(request,check_result));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Set order filling type according to symbol filling mode |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::SetTypeFillingBySymbol(const string symbol)
|
|
{
|
|
//--- get possible filling policy types by symbol
|
|
uint filling=(uint)SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE);
|
|
if((filling&SYMBOL_FILLING_FOK)==SYMBOL_FILLING_FOK)
|
|
{
|
|
m_type_filling=ORDER_FILLING_FOK;
|
|
return(true);
|
|
}
|
|
if((filling&SYMBOL_FILLING_IOC)==SYMBOL_FILLING_IOC)
|
|
{
|
|
m_type_filling=ORDER_FILLING_IOC;
|
|
return(true);
|
|
}
|
|
//---
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks and corrects type of filling policy |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::FillingCheck(const string symbol)
|
|
{
|
|
//--- get execution mode of orders by symbol
|
|
ENUM_SYMBOL_TRADE_EXECUTION exec=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(symbol,SYMBOL_TRADE_EXEMODE);
|
|
//--- check execution mode
|
|
if(exec==SYMBOL_TRADE_EXECUTION_REQUEST || exec==SYMBOL_TRADE_EXECUTION_INSTANT)
|
|
{
|
|
//--- neccessary filling type will be placed automatically
|
|
return(true);
|
|
}
|
|
//--- get possible filling policy types by symbol
|
|
uint filling=(uint)SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE);
|
|
//--- check execution mode again
|
|
if(exec==SYMBOL_TRADE_EXECUTION_MARKET)
|
|
{
|
|
//--- for the MARKET execution mode
|
|
//--- analyze order
|
|
if(m_request.action!=TRADE_ACTION_PENDING)
|
|
{
|
|
//--- in case of instant execution order
|
|
//--- if the required filling policy is supported, add it to the request
|
|
if((filling&SYMBOL_FILLING_FOK)==SYMBOL_FILLING_FOK)
|
|
{
|
|
m_type_filling=ORDER_FILLING_FOK;
|
|
m_request.type_filling=m_type_filling;
|
|
return(true);
|
|
}
|
|
if((filling&SYMBOL_FILLING_IOC)==SYMBOL_FILLING_IOC)
|
|
{
|
|
m_type_filling=ORDER_FILLING_IOC;
|
|
m_request.type_filling=m_type_filling;
|
|
return(true);
|
|
}
|
|
//--- wrong filling policy, set error code
|
|
m_result.retcode=TRADE_RETCODE_INVALID_FILL;
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
//--- EXCHANGE execution mode
|
|
switch(m_type_filling)
|
|
{
|
|
case ORDER_FILLING_FOK:
|
|
//--- analyze order
|
|
if(m_request.action==TRADE_ACTION_PENDING)
|
|
{
|
|
//--- in case of pending order
|
|
//--- add the expiration mode to the request
|
|
if(!ExpirationCheck(symbol))
|
|
m_request.type_time=ORDER_TIME_DAY;
|
|
//--- stop order?
|
|
if(m_request.type==ORDER_TYPE_BUY_STOP || m_request.type==ORDER_TYPE_SELL_STOP ||
|
|
m_request.type==ORDER_TYPE_BUY_LIMIT || m_request.type==ORDER_TYPE_SELL_LIMIT)
|
|
{
|
|
//--- in case of stop order
|
|
//--- add the corresponding filling policy to the request
|
|
m_request.type_filling=ORDER_FILLING_RETURN;
|
|
return(true);
|
|
}
|
|
}
|
|
//--- in case of limit order or instant execution order
|
|
//--- if the required filling policy is supported, add it to the request
|
|
if((filling&SYMBOL_FILLING_FOK)==SYMBOL_FILLING_FOK)
|
|
{
|
|
m_request.type_filling=m_type_filling;
|
|
return(true);
|
|
}
|
|
//--- wrong filling policy, set error code
|
|
m_result.retcode=TRADE_RETCODE_INVALID_FILL;
|
|
return(false);
|
|
case ORDER_FILLING_IOC:
|
|
//--- analyze order
|
|
if(m_request.action==TRADE_ACTION_PENDING)
|
|
{
|
|
//--- in case of pending order
|
|
//--- add the expiration mode to the request
|
|
if(!ExpirationCheck(symbol))
|
|
m_request.type_time=ORDER_TIME_DAY;
|
|
//--- stop order?
|
|
if(m_request.type==ORDER_TYPE_BUY_STOP || m_request.type==ORDER_TYPE_SELL_STOP ||
|
|
m_request.type==ORDER_TYPE_BUY_LIMIT || m_request.type==ORDER_TYPE_SELL_LIMIT)
|
|
{
|
|
//--- in case of stop order
|
|
//--- add the corresponding filling policy to the request
|
|
m_request.type_filling=ORDER_FILLING_RETURN;
|
|
return(true);
|
|
}
|
|
}
|
|
//--- in case of limit order or instant execution order
|
|
//--- if the required filling policy is supported, add it to the request
|
|
if((filling&SYMBOL_FILLING_IOC)==SYMBOL_FILLING_IOC)
|
|
{
|
|
m_request.type_filling=m_type_filling;
|
|
return(true);
|
|
}
|
|
//--- wrong filling policy, set error code
|
|
m_result.retcode=TRADE_RETCODE_INVALID_FILL;
|
|
return(false);
|
|
case ORDER_FILLING_RETURN:
|
|
//--- add filling policy to the request
|
|
m_request.type_filling=m_type_filling;
|
|
return(true);
|
|
}
|
|
//--- unknown execution mode, set error code
|
|
m_result.retcode=TRADE_RETCODE_ERROR;
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Check expiration type of pending order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::ExpirationCheck(const string symbol)
|
|
{
|
|
//--- check symbol
|
|
string symbol_name=(symbol==NULL) ? _Symbol : symbol;
|
|
//--- get flags
|
|
long tmp_long;
|
|
int flags=0;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_EXPIRATION_MODE,tmp_long))
|
|
flags=(int)tmp_long;
|
|
//--- check type
|
|
switch(m_request.type_time)
|
|
{
|
|
case ORDER_TIME_GTC:
|
|
if((flags&SYMBOL_EXPIRATION_GTC)!=0)
|
|
return(true);
|
|
break;
|
|
case ORDER_TIME_DAY:
|
|
if((flags&SYMBOL_EXPIRATION_DAY)!=0)
|
|
return(true);
|
|
break;
|
|
case ORDER_TIME_SPECIFIED:
|
|
if((flags&SYMBOL_EXPIRATION_SPECIFIED)!=0)
|
|
return(true);
|
|
break;
|
|
case ORDER_TIME_SPECIFIED_DAY:
|
|
if((flags&SYMBOL_EXPIRATION_SPECIFIED_DAY)!=0)
|
|
return(true);
|
|
break;
|
|
default:
|
|
Print(__FUNCTION__+": Unknown expiration type");
|
|
}
|
|
//--- failed
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderTypeCheck(const string symbol)
|
|
{
|
|
bool res=false;
|
|
//--- check symbol
|
|
string symbol_name=(symbol==NULL) ? _Symbol : symbol;
|
|
//--- get flags of allowed trade orders
|
|
long tmp_long;
|
|
int flags=0;
|
|
if(SymbolInfoInteger(symbol_name,SYMBOL_ORDER_MODE,tmp_long))
|
|
flags=(int)tmp_long;
|
|
//--- depending on the type of order in request
|
|
switch(m_request.type)
|
|
{
|
|
case ORDER_TYPE_BUY:
|
|
case ORDER_TYPE_SELL:
|
|
//--- check possibility of execution
|
|
res=((flags&SYMBOL_ORDER_MARKET)!=0);
|
|
break;
|
|
case ORDER_TYPE_BUY_LIMIT:
|
|
case ORDER_TYPE_SELL_LIMIT:
|
|
//--- check possibility of execution
|
|
res=((flags&SYMBOL_ORDER_LIMIT)!=0);
|
|
break;
|
|
case ORDER_TYPE_BUY_STOP:
|
|
case ORDER_TYPE_SELL_STOP:
|
|
//--- check possibility of execution
|
|
res=((flags&SYMBOL_ORDER_STOP)!=0);
|
|
break;
|
|
case ORDER_TYPE_BUY_STOP_LIMIT:
|
|
case ORDER_TYPE_SELL_STOP_LIMIT:
|
|
//--- check possibility of execution
|
|
res=((flags&SYMBOL_ORDER_STOP_LIMIT)!=0);
|
|
break;
|
|
}
|
|
//--- check res
|
|
if(res)
|
|
{
|
|
//--- trading order is valid
|
|
//--- check if we need and able to set protective orders
|
|
if(m_request.sl!=0.0 || m_request.tp!=0.0)
|
|
{
|
|
if((flags&SYMBOL_ORDER_SL)==0)
|
|
m_request.sl=0.0;
|
|
if((flags&SYMBOL_ORDER_TP)==0)
|
|
m_request.tp=0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- trading order is not valid
|
|
//--- set error
|
|
m_result.retcode=TRADE_RETCODE_INVALID_ORDER;
|
|
Print(__FUNCTION__+": Invalid order type");
|
|
}
|
|
//--- result
|
|
return(res);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send order |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::OrderSend(const MqlTradeRequest &request,MqlTradeResult &result)
|
|
{
|
|
bool res;
|
|
string action="";
|
|
string fmt ="";
|
|
//--- action
|
|
if(m_async_mode)
|
|
res=::OrderSendAsync(request,result);
|
|
else
|
|
res=::OrderSend(request,result);
|
|
//--- check
|
|
if(res)
|
|
{
|
|
if(m_log_level>LOG_LEVEL_ERRORS)
|
|
PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result));
|
|
}
|
|
else
|
|
{
|
|
if(m_log_level>LOG_LEVEL_NO)
|
|
PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result));
|
|
}
|
|
//--- return the result
|
|
return(res);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Position select depending on netting or hedging |
|
|
//+------------------------------------------------------------------+
|
|
bool CTrade::SelectPosition(const string symbol)
|
|
{
|
|
bool res=false;
|
|
//---
|
|
if(IsHedging())
|
|
{
|
|
uint total=PositionsTotal();
|
|
for(uint i=0; i<total; i++)
|
|
{
|
|
string position_symbol=PositionGetSymbol(i);
|
|
if(position_symbol==symbol && m_magic==PositionGetInteger(POSITION_MAGIC))
|
|
{
|
|
res=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
res=PositionSelect(symbol);
|
|
//---
|
|
return(res);
|
|
}
|
|
//+------------------------------------------------------------------+
|