forked from rosh/MQL5Book
1181 lines
39 KiB
MQL5
1181 lines
39 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| CustomTrade.mqh |
| |
//| Copyright © 2024, MetaQuotes Ltd. |
| |
//| https://www.mql5.com/ |
| |
//| |
| |
//| Simulate trade operations, market and pending orders |
| |
//| |
| |
//| status: experimental |
| |
//+------------------------------------------------------------------+
| |
| |
#include "AutoPtr.mqh"
| |
#include "Defines.mqh"
| |
#include "MarginProfitMeter.mqh"
| |
| |
#define HistorySelect CustomTrade::MT5HistorySelect
| |
#define HistorySelectByPosition CustomTrade::MT5HistorySelectByPosition
| |
#define PositionGetInteger CustomTrade::MT5PositionGetInteger
| |
#define PositionGetDouble CustomTrade::MT5PositionGetDouble
| |
#define PositionGetString CustomTrade::MT5PositionGetString
| |
#define PositionSelect CustomTrade::MT5PositionSelect
| |
#define PositionSelectByTicket CustomTrade::MT5PositionSelectByTicket
| |
#define PositionsTotal CustomTrade::MT5PositionsTotal
| |
#define OrdersTotal CustomTrade::MT5OrdersTotal
| |
#define PositionGetSymbol CustomTrade::MT5PositionGetSymbol
| |
#define PositionGetTicket CustomTrade::MT5PositionGetTicket
| |
#define HistoryDealsTotal CustomTrade::MT5HistoryDealsTotal
| |
#define HistoryOrdersTotal CustomTrade::MT5HistoryOrdersTotal
| |
#define HistoryDealGetTicket CustomTrade::MT5HistoryDealGetTicket
| |
#define HistoryOrderGetTicket CustomTrade::MT5HistoryOrderGetTicket
| |
#define HistoryDealGetInteger CustomTrade::MT5HistoryDealGetInteger
| |
#define HistoryDealGetDouble CustomTrade::MT5HistoryDealGetDouble
| |
#define HistoryDealGetString CustomTrade::MT5HistoryDealGetString
| |
#define HistoryOrderGetDouble CustomTrade::MT5HistoryOrderGetDouble
| |
#define HistoryOrderGetInteger CustomTrade::MT5HistoryOrderGetInteger
| |
#define HistoryOrderGetString CustomTrade::MT5HistoryOrderGetString
| |
#define OrderSend CustomTrade::MT5OrderSend
| |
#define OrderSelect CustomTrade::MT5OrderSelect
| |
#define HistoryOrderSelect CustomTrade::MT5HistoryOrderSelect
| |
#define HistoryDealSelect CustomTrade::MT5HistoryDealSelect
| |
// don't use standard TimeCurrent in virtual back tests!
| |
#define TimeCurrent CustomTrade::MT5TimeCurrent
| |
#define DAY_LONG (60 * 60 * 24)
| |
| |
#include "TradeBaseMonitor.mqh"
| |
//+------------------------------------------------------------------+
| |
//| Implementation limitations |
| |
//| - no rejects or deviations |
| |
//| - no partial execution |
| |
//| - no support for all trade actions |
| |
//| - no fees etc |
| |
//+------------------------------------------------------------------+
| |
| |
//+------------------------------------------------------------------+
| |
//| CustomTrade namespace |
| |
//+------------------------------------------------------------------+
| |
namespace CustomTrade
| |
{
| |
| |
//+------------------------------------------------------------------+
| |
//| CustomOrder |
| |
//+------------------------------------------------------------------+
| |
class CustomOrder: public MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>::TradeState
| |
{
| |
static long ticket;
| |
static long done;
| |
public:
| |
CustomOrder(const ENUM_ORDER_TYPE type, const double volume, const string symbol)
| |
{
| |
_set(ORDER_TYPE, type);
| |
_set(ORDER_TICKET, ++ticket);
| |
_set(ORDER_TIME_SETUP, SymbolInfoInteger(symbol, SYMBOL_TIME));
| |
_set(ORDER_TIME_SETUP_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
| |
if(type <= ORDER_TYPE_SELL)
| |
{
| |
// TODO: emulate deferred execution
| |
setDone(ORDER_STATE_FILLED);
| |
}
| |
else
| |
{
| |
_set(ORDER_STATE, ORDER_STATE_PLACED);
| |
}
| |
| |
_set(ORDER_VOLUME_INITIAL, volume);
| |
_set(ORDER_VOLUME_CURRENT, volume);
| |
| |
_set(ORDER_SYMBOL, symbol); // support multi-symbol
| |
}
| |
| |
void setDone(const ENUM_ORDER_STATE state)
| |
{
| |
const string symbol = _get<string>(ORDER_SYMBOL);
| |
_set(ORDER_TIME_DONE, SymbolInfoInteger(symbol, SYMBOL_TIME));
| |
_set(ORDER_TIME_DONE_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
| |
_set(ORDER_STATE, state);
| |
++done;
| |
}
| |
| |
bool isActive() const
| |
{
| |
return _get<long>(ORDER_TIME_DONE) == 0;
| |
}
| |
| |
static long getDoneCount()
| |
{
| |
return done;
| |
}
| |
};
| |
| |
static long CustomOrder::ticket = 0;
| |
static long CustomOrder::done = 0;
| |
| |
//+------------------------------------------------------------------+
| |
//| CustomDeal |
| |
//+------------------------------------------------------------------+
| |
class CustomDeal: public MonitorInterface<ENUM_DEAL_PROPERTY_INTEGER,ENUM_DEAL_PROPERTY_DOUBLE,ENUM_DEAL_PROPERTY_STRING>::TradeState
| |
{
| |
static long ticket;
| |
public:
| |
const CustomOrder *order;
| |
CustomDeal(const CustomOrder *o, const ENUM_DEAL_ENTRY entry): order(o)
| |
{
| |
const string symbol = order._get<string>(ORDER_SYMBOL);
| |
_set(DEAL_TICKET, ++ticket);
| |
_set(DEAL_ORDER, order._get<long>(ORDER_TICKET));
| |
_set(DEAL_TIME, SymbolInfoInteger(symbol, SYMBOL_TIME));
| |
_set(DEAL_TIME_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
| |
_set(DEAL_TYPE, order._get<long>(ORDER_TYPE) % 2);
| |
_set(DEAL_ENTRY, entry);
| |
_set(DEAL_POSITION_ID, order._get<long>(ORDER_POSITION_ID));
| |
| |
_set(DEAL_VOLUME, order._get<double>(ORDER_VOLUME_CURRENT));
| |
_set(DEAL_PRICE, order._get<double>(ORDER_PRICE_CURRENT));
| |
| |
_set(DEAL_SYMBOL, order._get<string>(ORDER_SYMBOL));
| |
}
| |
};
| |
| |
static long CustomDeal::ticket = 0;
| |
| |
//+------------------------------------------------------------------+
| |
//| CustomPosition |
| |
//+------------------------------------------------------------------+
| |
class CustomPosition: public MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>::TradeState
| |
{
| |
static long ticket;
| |
public:
| |
CustomPosition(CustomDeal *deal)
| |
{
| |
const string symbol = deal._get<string>(DEAL_SYMBOL);
| |
_set(POSITION_TICKET, ++ticket);
| |
_set(POSITION_IDENTIFIER, ticket);
| |
_set(POSITION_TIME, SymbolInfoInteger(symbol, SYMBOL_TIME));
| |
_set(POSITION_TIME_MSC, SymbolInfoInteger(symbol, SYMBOL_TIME_MSC));
| |
_set(POSITION_TYPE, deal._get<long>(DEAL_TYPE));
| |
_set(POSITION_VOLUME, deal._get<double>(DEAL_VOLUME));
| |
_set(POSITION_PRICE_OPEN, deal._get<double>(DEAL_PRICE));
| |
_set(POSITION_PRICE_CURRENT, deal._get<double>(DEAL_PRICE));
| |
_set(POSITION_PROFIT, deal._get<double>(DEAL_PROFIT));
| |
_set(POSITION_SYMBOL, deal._get<string>(DEAL_SYMBOL));
| |
_set(POSITION_SL, deal._get<double>(DEAL_SL));
| |
_set(POSITION_TP, deal._get<double>(DEAL_TP));
| |
deal._set(DEAL_POSITION_ID, ticket);
| |
}
| |
| |
~CustomPosition()
| |
{
| |
DisplayTrades();
| |
}
| |
};
| |
| |
static long CustomPosition::ticket = 0;
| |
| |
//+------------------------------------------------------------------+
| |
//| Global arrays of orders, deals, positions |
| |
//+------------------------------------------------------------------+
| |
AutoPtr<CustomOrder> orders[];
| |
CustomOrder *selectedOrders[];
| |
CustomOrder *selectedOrder = NULL;
| |
| |
AutoPtr<CustomDeal> deals[];
| |
CustomDeal *selectedDeals[];
| |
CustomDeal *selectedDeal = NULL;
| |
| |
AutoPtr<CustomPosition> positions[];
| |
CustomPosition *selectedPosition = NULL;
| |
| |
//+------------------------------------------------------------------+
| |
//| Simulated replacement functions for built-in trade functions |
| |
//| All functions have "MT5" prefix in their names |
| |
//+------------------------------------------------------------------+
| |
| |
bool MT5HistorySelect(const datetime from_date, const datetime to_date)
| |
{
| |
CheckOrders();
| |
| |
ArrayResize(selectedOrders, 0);
| |
ArrayResize(selectedDeals, 0);
| |
| |
for(int i = 0; i < ArraySize(orders); i++)
| |
{
| |
CustomOrder *ptr = orders[i][];
| |
if(!ptr.isActive())
| |
{
| |
if(ptr._get<long>(ORDER_TIME_SETUP) >= from_date
| |
|| ptr._get<long>(ORDER_TIME_DONE) <= to_date)
| |
{
| |
PUSH(selectedOrders, ptr);
| |
}
| |
}
| |
}
| |
| |
for(int i = 0; i < ArraySize(deals); i++)
| |
{
| |
CustomDeal *ptr = deals[i][];
| |
if(ptr._get<long>(DEAL_TIME) >= from_date
| |
|| ptr._get<long>(DEAL_TIME) <= to_date)
| |
{
| |
PUSH(selectedDeals, ptr);
| |
}
| |
}
| |
return true;
| |
}
| |
| |
bool MT5HistorySelectByPosition(long id)
| |
{
| |
CheckOrders();
| |
| |
ArrayResize(selectedOrders, 0);
| |
ArrayResize(selectedDeals, 0);
| |
| |
for(int i = 0; i < ArraySize(orders); i++)
| |
{
| |
CustomOrder *ptr = orders[i][];
| |
if(!ptr.isActive())
| |
{
| |
if(ptr._get<long>(ORDER_POSITION_ID) == id)
| |
{
| |
PUSH(selectedOrders, ptr);
| |
}
| |
}
| |
}
| |
| |
for(int i = 0; i < ArraySize(deals); i++)
| |
{
| |
CustomDeal *ptr = deals[i][];
| |
if(ptr._get<long>(DEAL_POSITION_ID) == id)
| |
{
| |
PUSH(selectedDeals, ptr);
| |
}
| |
}
| |
return true;
| |
}
| |
| |
bool MT5OrderSend(const MqlTradeRequest &req, MqlTradeResult &result)
| |
{
| |
MqlTradeRequest request = req;
| |
CustomOrder *order = NULL;
| |
CustomDeal *deal = NULL;
| |
CustomPosition *position = NULL;
| |
bool increasing = false;
| |
static uint reqid = 0;
| |
result.request_id = (uint)++reqid;
| |
result.retcode = 0;
| |
ResetLastError();
| |
| |
if(request.action == TRADE_ACTION_DEAL || request.action == TRADE_ACTION_PENDING)
| |
{
| |
if(request.price == 0)
| |
{
| |
if(request.type == ORDER_TYPE_BUY)
| |
{
| |
((MqlTradeRequest)request).price = SymbolInfoDouble(request.symbol, SYMBOL_ASK);
| |
}
| |
else
| |
if(request.type == ORDER_TYPE_SELL)
| |
{
| |
((MqlTradeRequest)request).price = SymbolInfoDouble(request.symbol, SYMBOL_BID);
| |
}
| |
}
| |
| |
if(request.action == TRADE_ACTION_DEAL)
| |
{
| |
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
| |
&& request.position == 0
| |
&& MT5PositionSelect(request.symbol))
| |
{
| |
request.position = selectedPosition._get<long>(POSITION_TICKET);
| |
}
| |
}
| |
| |
if(request.position > 0) // exit market or reverse or adding volume
| |
{
| |
int index = FindPositionIndex(request.position);
| |
if(index == -1)
| |
{
| |
SetUserError(TRADE_RETCODE_POSITION_CLOSED);
| |
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
| |
return false;
| |
}
| |
CustomPosition *p = positions[index][];
| |
| |
if(request.type == p._get<long>(POSITION_TYPE)) // adding volume (only netting)
| |
{
| |
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
| |
{
| |
SetUserError(TRADE_RETCODE_HEDGE_PROHIBITED);
| |
result.retcode = TRADE_RETCODE_HEDGE_PROHIBITED;
| |
return false;
| |
}
| |
increasing = true;
| |
selectedPosition = p;
| |
}
| |
else // exit market or reversing
| |
{
| |
const double v = p._get<double>(POSITION_VOLUME);
| |
deal = PositionClose(p, request.volume, request.price);
| |
if(!deal)
| |
{
| |
result.retcode = _LastError - ERR_USER_ERROR_FIRST;
| |
}
| |
else
| |
{
| |
if(TU::Equal(v, request.volume))
| |
{
| |
Print("Close position: ", request.position);
| |
ArrayRemove(positions, index, 1); // remove 'p' from 'positions'
| |
}
| |
order = (CustomOrder *)deal.order;
| |
result.retcode = TRADE_RETCODE_DONE;
| |
result.deal = deal._get<long>(DEAL_TICKET);
| |
result.order = order._get<long>(ORDER_TICKET);
| |
result.volume = request.volume;
| |
result.price = request.price;
| |
}
| |
}
| |
}
| |
| |
if(request.position == 0 // enter market or set pending order
| |
|| increasing) // adding volume for netting
| |
{
| |
// TODO: check request.price
| |
order = new CustomOrder(request.type, request.volume, request.symbol);
| |
order._set(ORDER_PRICE_OPEN, request.price);
| |
order._set(ORDER_PRICE_CURRENT, request.price);
| |
order._set(ORDER_SL, request.sl);
| |
order._set(ORDER_TP, request.tp);
| |
order._set(ORDER_TIME_EXPIRATION, request.expiration);
| |
order._set(ORDER_TYPE_TIME, request.expiration ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC);
| |
| |
result.retcode = TRADE_RETCODE_DONE;
| |
result.order = order._get<long>(ORDER_TICKET);
| |
result.ask = SymbolInfoDouble(request.symbol, SYMBOL_ASK);
| |
result.bid = SymbolInfoDouble(request.symbol, SYMBOL_BID);
| |
result.volume = request.volume;
| |
result.price = request.price;
| |
| |
if(request.action == TRADE_ACTION_DEAL)
| |
{
| |
deal = new CustomDeal(order, DEAL_ENTRY_IN);
| |
deal._set(DEAL_SL, order._get<double>(ORDER_SL));
| |
deal._set(DEAL_TP, order._get<double>(ORDER_TP));
| |
| |
if(!increasing)
| |
{
| |
position = new CustomPosition(deal);
| |
order._set(ORDER_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
}
| |
else
| |
{
| |
Increase(selectedPosition, request.volume, request.price);
| |
selectedPosition._set(POSITION_SL, order._get<double>(ORDER_SL));
| |
selectedPosition._set(POSITION_TP, order._get<double>(ORDER_TP));
| |
order._set(ORDER_POSITION_ID, selectedPosition._get<long>(POSITION_IDENTIFIER));
| |
}
| |
deal._set(DEAL_POSITION_ID, order._get<long>(ORDER_POSITION_ID));
| |
result.deal = deal._get<long>(DEAL_TICKET);
| |
}
| |
}
| |
}
| |
/*
| |
else if(request.action == TRADE_ACTION_SLTP)
| |
{
| |
// TODO: change opened position
| |
}
| |
else if(request.action == TRADE_ACTION_MODIFY)
| |
{
| |
// TODO: change pending order
| |
}
| |
else if(request.action == TRADE_ACTION_REMOVE)
| |
{
| |
// TODO: delete pending order
| |
}
| |
else if(request.action == TRADE_ACTION_CLOSE_BY)
| |
{
| |
// TODO: close 2 positions
| |
}
| |
*/
| |
| |
if(order == NULL) return false;
| |
| |
PUSH(orders, order);
| |
| |
if(deal != NULL)
| |
{
| |
PUSH(deals, deal);
| |
}
| |
| |
if(position != NULL)
| |
{
| |
PUSH(positions, position);
| |
}
| |
| |
return true;
| |
}
| |
| |
long MT5PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property_id)
| |
{
| |
long temp;
| |
if(MT5PositionGetInteger(property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property_id, long &long_var)
| |
{
| |
if(CheckPointer(selectedPosition) == POINTER_INVALID) return false;
| |
long_var = selectedPosition._get<long>(property_id);
| |
return true;
| |
}
| |
| |
double MT5PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property_id)
| |
{
| |
double temp;
| |
if(MT5PositionGetDouble(property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property_id, double &double_var)
| |
{
| |
if(CheckPointer(selectedPosition) == POINTER_INVALID) return false;
| |
double_var = selectedPosition._get<double>(property_id);
| |
return true;
| |
}
| |
| |
string MT5PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id)
| |
{
| |
string temp;
| |
if(MT5PositionGetString(property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return "";
| |
}
| |
| |
bool MT5PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id, string &string_var)
| |
{
| |
if(CheckPointer(selectedPosition) == POINTER_INVALID) return false;
| |
string_var = selectedPosition._get<string>(property_id);
| |
return true;
| |
}
| |
| |
bool MT5PositionSelect(string symbol)
| |
{
| |
selectedPosition = NULL;
| |
for(int i = 0; i < ArraySize(positions); i++)
| |
{
| |
CustomPosition *p = positions[i][];
| |
if(p._get<string>(POSITION_SYMBOL) == symbol)
| |
{
| |
selectedPosition = p;
| |
UpdatePosition(p);
| |
return true;
| |
}
| |
}
| |
return false;
| |
}
| |
| |
bool MT5PositionSelectByTicket(ulong ticket)
| |
{
| |
selectedPosition = NULL;
| |
for(int i = 0; i < ArraySize(positions); i++)
| |
{
| |
CustomPosition *p = positions[i][];
| |
if(p._get<long>(POSITION_TICKET) == ticket)
| |
{
| |
selectedPosition = p;
| |
UpdatePosition(p);
| |
return true;
| |
}
| |
}
| |
return false;
| |
}
| |
| |
int MT5PositionsTotal()
| |
{
| |
CheckOrders();
| |
return ArraySize(positions);
| |
}
| |
| |
int MT5OrdersTotal()
| |
{
| |
CheckOrders();
| |
return ArraySize(orders) - (int)CustomOrder::getDoneCount();
| |
}
| |
| |
string MT5PositionGetSymbol(int index)
| |
{
| |
selectedPosition = NULL;
| |
if(index < 0 || index >= ArraySize(positions)) return NULL;
| |
| |
CustomPosition *p = positions[index][];
| |
selectedPosition = p;
| |
UpdatePosition(p);
| |
return p._get<string>(POSITION_SYMBOL);
| |
}
| |
| |
ulong MT5PositionGetTicket(int index)
| |
{
| |
selectedPosition = NULL;
| |
if(index < 0 || index >= ArraySize(positions)) return 0;
| |
| |
CustomPosition *p = positions[index][];
| |
selectedPosition = p;
| |
UpdatePosition(p);
| |
return p._get<long>(POSITION_TICKET);
| |
}
| |
| |
int MT5HistoryDealsTotal()
| |
{
| |
return ArraySize(selectedDeals);
| |
}
| |
| |
int MT5HistoryOrdersTotal()
| |
{
| |
return ArraySize(selectedOrders);
| |
}
| |
| |
ulong MT5HistoryDealGetTicket(int index)
| |
{
| |
if(index < 0 || index >= ArraySize(selectedDeals)) return 0;
| |
selectedDeal = selectedDeals[index];
| |
return selectedDeal._get<long>(DEAL_TICKET);
| |
}
| |
| |
ulong MT5HistoryOrderGetTicket(int index)
| |
{
| |
if(index < 0 || index >= ArraySize(selectedOrders)) return 0;
| |
selectedOrder = selectedOrders[index];
| |
return selectedOrder._get<long>(ORDER_TICKET);
| |
}
| |
| |
long MT5HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property_id)
| |
{
| |
long temp;
| |
if(MT5HistoryDealGetInteger(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property_id, long &long_var)
| |
{
| |
if(CheckPointer(selectedDeal) != POINTER_INVALID
| |
&& selectedDeal._get<long>(DEAL_TICKET) == ticket)
| |
{
| |
selectedDeal._get(property_id, long_var);
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
double MT5HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property_id)
| |
{
| |
double temp;
| |
if(MT5HistoryDealGetDouble(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property_id, double &double_var)
| |
{
| |
if(CheckPointer(selectedDeal) != POINTER_INVALID
| |
&& selectedDeal._get<long>(DEAL_TICKET) == ticket)
| |
{
| |
selectedDeal._get(property_id, double_var);
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
string MT5HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property_id)
| |
{
| |
string temp;
| |
if(MT5HistoryDealGetString(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return "";
| |
}
| |
| |
bool MT5HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property_id, string &string_var)
| |
{
| |
if(CheckPointer(selectedDeal) != POINTER_INVALID
| |
&& selectedDeal._get<long>(DEAL_TICKET) == ticket)
| |
{
| |
selectedDeal._get(property_id, string_var);
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
double MT5HistoryOrderGetDouble(ulong ticket, ENUM_ORDER_PROPERTY_DOUBLE property_id)
| |
{
| |
double temp;
| |
if(MT5HistoryOrderGetDouble(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5HistoryOrderGetDouble(ulong ticket, ENUM_ORDER_PROPERTY_DOUBLE property_id, double &double_var)
| |
{
| |
if(CheckPointer(selectedOrder) != POINTER_INVALID
| |
&& selectedOrder._get<long>(ORDER_TICKET) == ticket)
| |
{
| |
if(property_id == ORDER_PRICE_CURRENT)
| |
{
| |
double_var = SymbolInfoDouble(selectedOrder._get<string>(ORDER_SYMBOL),
| |
(selectedOrder._get<long>(ORDER_TYPE) % 2) == 0 ? SYMBOL_BID : SYMBOL_ASK);
| |
}
| |
else
| |
{
| |
selectedOrder._get(property_id, double_var);
| |
}
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
long MT5HistoryOrderGetInteger(ulong ticket, ENUM_ORDER_PROPERTY_INTEGER property_id)
| |
{
| |
long temp = 0;
| |
if(MT5HistoryOrderGetInteger(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return 0;
| |
}
| |
| |
bool MT5HistoryOrderGetInteger(ulong ticket, ENUM_ORDER_PROPERTY_INTEGER property_id, long &long_var)
| |
{
| |
if(CheckPointer(selectedOrder) != POINTER_INVALID
| |
&& selectedOrder._get<long>(ORDER_TICKET) == ticket)
| |
{
| |
selectedOrder._get(property_id, long_var);
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
string MT5HistoryOrderGetString(ulong ticket, ENUM_ORDER_PROPERTY_STRING property_id)
| |
{
| |
string temp;
| |
if(MT5HistoryOrderGetString(ticket, property_id, temp))
| |
{
| |
return temp;
| |
}
| |
return "";
| |
}
| |
| |
bool MT5HistoryOrderGetString(ulong ticket, ENUM_ORDER_PROPERTY_STRING property_id, string &string_var)
| |
{
| |
if(CheckPointer(selectedOrder) != POINTER_INVALID
| |
&& selectedOrder._get<long>(ORDER_TICKET) == ticket)
| |
{
| |
selectedOrder._get(property_id, string_var);
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
bool MT5OrderSelect(ulong ticket)
| |
{
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
CustomOrder *o = orders[i][];
| |
if(o.isActive() && o._get<long>(ORDER_TICKET) == ticket)
| |
{
| |
selectedOrder = o;
| |
return true;
| |
}
| |
}
| |
return false;
| |
}
| |
| |
bool MT5HistoryOrderSelect(ulong ticket)
| |
{
| |
ArrayResize(selectedOrders, 0);
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
CustomOrder *o = orders[i][];
| |
if(!o.isActive() && o._get<long>(ORDER_TICKET) == ticket)
| |
{
| |
PUSH(selectedOrders, o);
| |
selectedOrder = o;
| |
return true;
| |
}
| |
}
| |
return false;
| |
}
| |
| |
bool MT5HistoryDealSelect(ulong ticket)
| |
{
| |
ArrayResize(selectedDeals, 0);
| |
for(int i = 0; i < ArraySize(deals); ++i)
| |
{
| |
CustomDeal *d = deals[i][];
| |
if(d._get<long>(DEAL_TICKET) == ticket)
| |
{
| |
PUSH(selectedDeals, d);
| |
selectedDeal = d;
| |
return true;
| |
}
| |
}
| |
return false;
| |
}
| |
| |
datetime MT5TimeCurrent()
| |
{
| |
return (datetime)SymbolInfoInteger(_Symbol, SYMBOL_TIME);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Main custom functions for updating and exposing trade state |
| |
//+------------------------------------------------------------------+
| |
| |
void CheckPositions()
| |
{
| |
for(int i = ArraySize(positions) - 1; i >= 0; --i)
| |
{
| |
UpdatePosition(positions[i][]);
| |
}
| |
}
| |
| |
void UpdatePosition(CustomPosition *position)
| |
{
| |
if(CheckPointer(position) == POINTER_INVALID) return;
| |
| |
const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)position._get<long>(POSITION_TYPE);
| |
const string symbol = position._get<string>(POSITION_SYMBOL);
| |
const double price = SymbolInfoDouble(symbol,
| |
type == POSITION_TYPE_BUY ? SYMBOL_BID : SYMBOL_ASK);
| |
position._set(POSITION_PRICE_CURRENT, price);
| |
| |
double profit = (price - position._get<double>(POSITION_PRICE_OPEN))
| |
/ SymbolInfoDouble(symbol, SYMBOL_POINT)
| |
* position._get<double>(POSITION_VOLUME) * MPM::PointValue(symbol);
| |
if(type == POSITION_TYPE_SELL)
| |
{
| |
profit *= -1;
| |
}
| |
| |
position._set(POSITION_PROFIT, profit);
| |
| |
const double swap = SymbolInfoDouble(symbol,
| |
type == POSITION_TYPE_BUY ? SYMBOL_SWAP_LONG : SYMBOL_SWAP_SHORT);
| |
| |
ENUM_SYMBOL_INFO_DOUBLE swaps[7] = {SYMBOL_SWAP_SUNDAY, SYMBOL_SWAP_MONDAY, SYMBOL_SWAP_TUESDAY, SYMBOL_SWAP_WEDNESDAY, SYMBOL_SWAP_THURSDAY, SYMBOL_SWAP_FRIDAY, SYMBOL_SWAP_SATURDAY};
| |
double total = 0;
| |
datetime start = (datetime)((position._get<long>(POSITION_TIME) / DAY_LONG + 1) * DAY_LONG);
| |
for( ; start < SymbolInfoInteger(symbol, SYMBOL_TIME); start += DAY_LONG)
| |
{
| |
MqlDateTime day;
| |
TimeToStruct(start, day);
| |
total += SymbolInfoDouble(symbol, swaps[day.day_of_week]) * swap;
| |
}
| |
// TODO: utilize SYMBOL_SWAP_MODE, for example,
| |
// if SYMBOL_SWAP_MODE_POINTS used, MPM::PointValue(symbol) * swap * lot
| |
// NB: this calculation may be tricky because position
| |
// may change volume during lifetime
| |
position._set(POSITION_SWAP, total);
| |
| |
CustomDeal *deal = NULL;
| |
| |
if(type == POSITION_TYPE_BUY)
| |
{
| |
if(price <= position._get<double>(POSITION_SL))
| |
{
| |
deal = PositionClose(position, position._get<double>(POSITION_VOLUME), price, "[sl]");
| |
}
| |
else if(position._get<double>(POSITION_TP) > 0.0 && price >= position._get<double>(POSITION_TP))
| |
{
| |
deal = PositionClose(position, position._get<double>(POSITION_VOLUME), price, "[tp]");
| |
}
| |
}
| |
else
| |
{
| |
if(position._get<double>(POSITION_SL) != 0.0 && price >= position._get<double>(POSITION_SL))
| |
{
| |
deal = PositionClose(position, position._get<double>(POSITION_VOLUME), price, "[sl]");
| |
}
| |
else if(price <= position._get<double>(POSITION_TP))
| |
{
| |
deal = PositionClose(position, position._get<double>(POSITION_VOLUME), price, "[tp]");
| |
}
| |
}
| |
if(deal)
| |
{
| |
if(deal.order)
| |
{
| |
PUSH(orders, deal.order);
| |
}
| |
PUSH(deals, deal);
| |
| |
int index = FindPositionIndex(position._get<long>(POSITION_TICKET));
| |
Print("SL/TP position: ", position._get<long>(POSITION_TICKET));
| |
if(index != -1)
| |
{
| |
ArrayRemove(positions, index, 1);
| |
}
| |
}
| |
}
| |
| |
CustomDeal *PositionClose(CustomPosition *position, const double volume, const double price,
| |
const string comment = NULL, CustomOrder *order = NULL, CustomDeal *deal = NULL)
| |
{
| |
// TODO: check price
| |
if(volume > position._get<double>(POSITION_VOLUME)) // this is turn around (netting only)
| |
{
| |
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
| |
{
| |
SetUserError(TRADE_RETCODE_INVALID_CLOSE_VOLUME);
| |
return NULL;
| |
}
| |
}
| |
| |
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)!position._get<long>(POSITION_TYPE);
| |
const string symbol = position._get<string>(POSITION_SYMBOL);
| |
| |
if(!order) order = new CustomOrder(type, volume, symbol);
| |
order._set(ORDER_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
order._set(ORDER_PRICE_OPEN, price);
| |
order._set(ORDER_PRICE_CURRENT, price);
| |
| |
double remain = position._get<double>(POSITION_VOLUME) - volume;
| |
double actual = fmin(volume, position._get<double>(POSITION_VOLUME));
| |
remain = NormalizeDouble(remain, -(int)MathLog10(SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)));
| |
| |
double profit = (price - position._get<double>(POSITION_PRICE_OPEN)) / SymbolInfoDouble(symbol, SYMBOL_POINT)
| |
* actual * MPM::PointValue(symbol);
| |
| |
if(position._get<long>(POSITION_TYPE) == POSITION_TYPE_SELL)
| |
{
| |
profit *= -1;
| |
}
| |
| |
position._set(POSITION_PRICE_CURRENT, price);
| |
if(remain > 0) // partial close
| |
{
| |
Print("partial close ", remain);
| |
position._set(POSITION_VOLUME, remain);
| |
position._set(POSITION_PROFIT, profit * remain / volume);
| |
}
| |
else if(remain < 0) // reversal
| |
{
| |
Print("reversal ", remain);
| |
position._set(POSITION_VOLUME, -remain);
| |
position._set(POSITION_TICKET, order._get<long>(ORDER_TICKET));
| |
position._set(POSITION_PROFIT, 0);
| |
position._set(POSITION_TYPE, (ENUM_POSITION_TYPE)type);
| |
position._set(POSITION_PRICE_OPEN, price);
| |
}
| |
else // complete close
| |
{
| |
position._set(POSITION_PROFIT, profit);
| |
}
| |
| |
if(!deal) deal = new CustomDeal(order, DEAL_ENTRY_OUT);
| |
deal._set(DEAL_PROFIT, profit);
| |
deal._set(DEAL_SWAP, position._get<double>(POSITION_SWAP));
| |
deal._set(DEAL_COMMENT, comment);
| |
| |
deal._set(DEAL_SL, position._get<double>(POSITION_SL));
| |
deal._set(DEAL_TP, position._get<double>(POSITION_TP));
| |
| |
position._set(POSITION_COMMENT, comment);
| |
position._set(POSITION_SWAP, 0);
| |
| |
return deal;
| |
}
| |
| |
void Increase(CustomPosition *position, const double volume, const double price)
| |
{
| |
double average = (position._get<double>(POSITION_PRICE_OPEN) * position._get<double>(POSITION_VOLUME)
| |
+ price * volume) / (position._get<double>(POSITION_VOLUME) + volume);
| |
| |
position._set(POSITION_PRICE_OPEN, average);
| |
position._set(POSITION_VOLUME, position._get<double>(POSITION_VOLUME) + volume);
| |
}
| |
| |
int FindPositionIndex(ulong ticket)
| |
{
| |
for(int i = 0; i < ArraySize(positions); i++)
| |
{
| |
if(positions[i][]._get<long>(POSITION_TICKET) == ticket)
| |
{
| |
return i;
| |
}
| |
}
| |
return -1;
| |
}
| |
| |
void MakeDeal(CustomOrder *order)
| |
{
| |
CustomDeal *deal = new CustomDeal(order, DEAL_ENTRY_IN);
| |
deal._set(DEAL_SL, order._get<double>(ORDER_SL));
| |
deal._set(DEAL_TP, order._get<double>(ORDER_TP));
| |
| |
CustomPosition *position = NULL;
| |
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
| |
|| !MT5PositionSelect(order._get<string>(ORDER_SYMBOL)))
| |
{
| |
position = new CustomPosition(deal);
| |
PUSH(positions, position);
| |
order._set(ORDER_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
deal._set(DEAL_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
}
| |
else // netting and selected
| |
{
| |
position = selectedPosition;
| |
order._set(ORDER_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
deal._set(DEAL_POSITION_ID, position._get<long>(POSITION_IDENTIFIER));
| |
if(order._get<long>(ORDER_TYPE) % 2 == position._get<long>(POSITION_TYPE))
| |
{
| |
// added volume
| |
Increase(position, order._get<double>(ORDER_VOLUME_INITIAL), order._get<double>(ORDER_PRICE_OPEN));
| |
}
| |
else // closure, partial closure or reversal
| |
{
| |
deal._set(DEAL_ENTRY, DEAL_ENTRY_OUT);
| |
const bool complete = TU::Equal(position._get<double>(POSITION_VOLUME), order._get<double>(ORDER_VOLUME_INITIAL));
| |
PositionClose(position, order._get<double>(ORDER_VOLUME_INITIAL), position._get<double>(POSITION_PRICE_CURRENT),
| |
NULL, order, deal);
| |
if(complete)
| |
{
| |
int index = FindPositionIndex(position._get<long>(POSITION_TICKET));
| |
if(index != -1)
| |
{
| |
ArrayRemove(positions, index, 1);
| |
}
| |
}
| |
}
| |
}
| |
order.setDone(ORDER_STATE_FILLED);
| |
PUSH(deals, deal);
| |
}
| |
| |
void CheckOrders()
| |
{
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
CustomOrder *o = orders[i][];
| |
if(o.isActive())
| |
{
| |
const string symbol = o._get<string>(ORDER_SYMBOL);
| |
const datetime expiration = (datetime)o._get<long>(ORDER_TIME_EXPIRATION);
| |
if(expiration && expiration > SymbolInfoInteger(symbol, SYMBOL_TIME))
| |
{
| |
o.setDone(ORDER_STATE_EXPIRED);
| |
}
| |
else
| |
switch((ENUM_ORDER_TYPE)o._get<long>(ORDER_TYPE))
| |
{
| |
case ORDER_TYPE_BUY_LIMIT:
| |
if(SymbolInfoDouble(symbol, SYMBOL_ASK) <= o._get<double>(ORDER_PRICE_OPEN))
| |
{
| |
Print("Buy limit triggered: ", o._get<long>(ORDER_TICKET));
| |
o._set(ORDER_COMMENT, "[buy limit]");
| |
MakeDeal(o);
| |
}
| |
break;
| |
case ORDER_TYPE_SELL_LIMIT:
| |
if(SymbolInfoDouble(symbol, SYMBOL_BID) >= o._get<double>(ORDER_PRICE_OPEN))
| |
{
| |
Print("Sell limit triggered: ", o._get<long>(ORDER_TICKET));
| |
o._set(ORDER_COMMENT, "[sell limit]");
| |
MakeDeal(o);
| |
}
| |
break;
| |
case ORDER_TYPE_BUY_STOP:
| |
if(SymbolInfoDouble(symbol, SYMBOL_ASK) >= o._get<double>(ORDER_PRICE_OPEN))
| |
{
| |
Print("Buy stop triggered: ", o._get<long>(ORDER_TICKET));
| |
o._set(ORDER_COMMENT, "[buy stop]");
| |
MakeDeal(o);
| |
}
| |
break;
| |
case ORDER_TYPE_SELL_STOP:
| |
if(SymbolInfoDouble(symbol, SYMBOL_BID) <= o._get<double>(ORDER_PRICE_OPEN))
| |
{
| |
Print("Sell stop triggered: ", o._get<long>(ORDER_TICKET));
| |
o._set(ORDER_COMMENT, "[sell stop]");
| |
MakeDeal(o);
| |
}
| |
break;
| |
}
| |
}
| |
}
| |
}
| |
| |
void DisplayTrades()
| |
{
| |
static const color colors[] = {clrBlue, clrRed};
| |
for(int i = 0; i < ArraySize(positions); ++i)
| |
{
| |
const long ticket = positions[i][]._get<long>(POSITION_TICKET);
| |
const string name = "pos" + (string)ticket;
| |
ObjectCreate(0, name, OBJ_ARROWED_LINE, 0,
| |
positions[i][]._get<long>(POSITION_TIME), positions[i][]._get<double>(POSITION_PRICE_OPEN));
| |
ObjectSetInteger(0, name, OBJPROP_TIME, 1, SymbolInfoInteger(_Symbol, SYMBOL_TIME));
| |
ObjectSetDouble(0, name, OBJPROP_PRICE, 1, positions[i][]._get<double>(POSITION_PRICE_CURRENT));
| |
| |
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
| |
ObjectSetInteger(0, name, OBJPROP_COLOR, colors[(int)positions[i][]._get<long>(POSITION_TYPE)]);
| |
ObjectSetString(0, name, OBJPROP_TEXT, StringFormat("#%lld %.2f",
| |
ticket, positions[i][]._get<double>(POSITION_PROFIT)));
| |
| |
if(positions[i][]._get<double>(POSITION_SL))
| |
{
| |
const string sl = "sl" + (string)ticket;
| |
ObjectCreate(0, sl, OBJ_HLINE, 0,
| |
0, positions[i][]._get<double>(POSITION_SL));
| |
ObjectSetInteger(0, sl, OBJPROP_ZORDER, ticket);
| |
ObjectSetInteger(0, sl, OBJPROP_COLOR, clrRed);
| |
ObjectSetInteger(0, sl, OBJPROP_STYLE, STYLE_DOT);
| |
ObjectSetString(0, sl, OBJPROP_TEXT, StringFormat("#%lld SL", ticket));
| |
}
| |
| |
if(positions[i][]._get<double>(POSITION_TP))
| |
{
| |
const string tp = "tp" + (string)ticket;
| |
ObjectCreate(0, tp, OBJ_HLINE, 0,
| |
0, positions[i][]._get<double>(POSITION_TP));
| |
ObjectSetInteger(0, tp, OBJPROP_ZORDER, ticket);
| |
ObjectSetInteger(0, tp, OBJPROP_COLOR, clrBlue);
| |
ObjectSetInteger(0, tp, OBJPROP_STYLE, STYLE_DOT);
| |
ObjectSetString(0, tp, OBJPROP_TEXT, StringFormat("#%lld TP", ticket));
| |
}
| |
}
| |
| |
for(int i = ObjectsTotal(0, 0, OBJ_HLINE) - 1; i >= 0; --i)
| |
{
| |
const string name = ObjectName(0, i, 0, OBJ_HLINE);
| |
const long ticket = ObjectGetInteger(0, name, OBJPROP_ZORDER);
| |
if(ticket != 0 && FindPositionIndex(ticket) == -1)
| |
{
| |
ObjectDelete(0, name);
| |
ObjectSetInteger(0, "pos" + (string)ticket, OBJPROP_STYLE, STYLE_DOT);
| |
}
| |
}
| |
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
const string name = "pen" + (string)orders[i][]._get<long>(ORDER_TICKET);
| |
if(orders[i][].isActive())
| |
{
| |
ObjectCreate(0, name,
| |
OBJ_HLINE, 0,
| |
0, orders[i][]._get<double>(ORDER_PRICE_OPEN));
| |
| |
ObjectSetInteger(0, name, OBJPROP_COLOR, colors[(int)orders[i][]._get<long>(ORDER_TYPE) % 2]);
| |
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASHDOT);
| |
ObjectSetString(0, name, OBJPROP_TEXT, StringFormat("#%lld",
| |
orders[i][]._get<long>(ORDER_TICKET)));
| |
}
| |
else
| |
{
| |
ObjectDelete(0, name);
| |
}
| |
}
| |
}
| |
| |
string ReportTradeState()
| |
{
| |
CheckPositions();
| |
CheckOrders();
| |
| |
static const string types[] = {"BUY ", "SELL"};
| |
string result = "Positions:" + (string)ArraySize(positions) + "\n";
| |
double total = 0;
| |
for(int i = 0; i < ArraySize(positions); ++i)
| |
{
| |
const string symbol = positions[i][]._get<string>(POSITION_SYMBOL);
| |
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
| |
int dots = (int)fmax(0, -log10(SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)));
| |
result += StringFormat("#%lld %s %s %.*f %.*f -> %.*f = %.2f\n",
| |
positions[i][]._get<long>(POSITION_TICKET),
| |
TimeToString(positions[i][]._get<long>(POSITION_TIME), TIME_DATE | TIME_SECONDS),
| |
types[(int)positions[i][]._get<long>(POSITION_TYPE)],
| |
dots, positions[i][]._get<double>(POSITION_VOLUME),
| |
digits, positions[i][]._get<double>(POSITION_PRICE_OPEN),
| |
digits, positions[i][]._get<double>(POSITION_PRICE_CURRENT),
| |
positions[i][]._get<double>(POSITION_PROFIT));
| |
total += positions[i][]._get<double>(POSITION_PROFIT);
| |
}
| |
result += "Total: " + StringFormat("%.2f\n", total);
| |
result += "Orders:" + (string)(ArraySize(orders) - CustomOrder::getDoneCount()) + "\n";
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
if(orders[i][].isActive())
| |
{
| |
const string symbol = orders[i][]._get<string>(ORDER_SYMBOL);
| |
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
| |
int dots = (int)fmax(0, -log10(SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)));
| |
result += StringFormat("#%lld %s %s %.*f %.*f\n",
| |
orders[i][]._get<long>(ORDER_TICKET),
| |
EnumToString((ENUM_ORDER_TYPE)orders[i][]._get<long>(ORDER_TYPE)),
| |
TimeToString(orders[i][]._get<long>(ORDER_TIME_SETUP), TIME_DATE | TIME_SECONDS),
| |
dots, orders[i][]._get<double>(ORDER_VOLUME_INITIAL),
| |
digits, orders[i][]._get<double>(ORDER_PRICE_OPEN));
| |
}
| |
}
| |
return result;
| |
}
| |
| |
#define STRNULL(S) (S == NULL ? "" : S)
| |
| |
void PrintTradeHistory()
| |
{
| |
Print("History Orders:");
| |
for(int i = 0; i < ArraySize(orders); ++i)
| |
{
| |
if(!orders[i][].isActive())
| |
{
| |
PrintFormat("(%lld) #%lld %s %s -> %s L=%g @ %g %s",
| |
orders[i][]._get<long>(ORDER_POSITION_ID),
| |
orders[i][]._get<long>(ORDER_TICKET),
| |
EnumToString((ENUM_ORDER_TYPE)orders[i][]._get<long>(ORDER_TYPE)),
| |
TimeToString(orders[i][]._get<long>(ORDER_TIME_SETUP), TIME_DATE | TIME_SECONDS),
| |
TimeToString(orders[i][]._get<long>(ORDER_TIME_DONE), TIME_DATE | TIME_SECONDS),
| |
orders[i][]._get<double>(ORDER_VOLUME_INITIAL),
| |
orders[i][]._get<double>(ORDER_PRICE_OPEN),
| |
STRNULL(orders[i][]._get<string>(ORDER_COMMENT)));
| |
}
| |
}
| |
double total = 0;
| |
int trades = 0;
| |
Print("Deals:");
| |
for(int i = 0; i < ArraySize(deals); ++i)
| |
{
| |
PrintFormat("(%lld) #%lld [#%lld] %s %s %s L=%g @ %g = %.2f %s",
| |
deals[i][]._get<long>(DEAL_POSITION_ID),
| |
deals[i][]._get<long>(DEAL_TICKET), deals[i][]._get<long>(DEAL_ORDER),
| |
EnumToString((ENUM_DEAL_TYPE)deals[i][]._get<long>(DEAL_TYPE)),
| |
EnumToString((ENUM_DEAL_ENTRY)deals[i][]._get<long>(DEAL_ENTRY)),
| |
TimeToString(deals[i][]._get<long>(DEAL_TIME), TIME_DATE | TIME_SECONDS),
| |
deals[i][]._get<double>(DEAL_VOLUME),
| |
deals[i][]._get<double>(DEAL_PRICE),
| |
deals[i][]._get<double>(DEAL_PROFIT),
| |
STRNULL(deals[i][]._get<string>(DEAL_COMMENT)));
| |
| |
if(deals[i][]._get<long>(DEAL_ENTRY) != DEAL_ENTRY_IN)
| |
{
| |
++trades;
| |
}
| |
total += deals[i][]._get<double>(DEAL_PROFIT); /* TODO: + deals[i][]._get<double>(DEAL_SWAP) + etc */
| |
}
| |
PrintFormat("Total: %.2f, Trades: %d", total, trades);
| |
}
| |
| |
} // end of the namespace CustomTrade
| |
//+------------------------------------------------------------------+
|