forked from airat77786/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
| |||
//+------------------------------------------------------------------+
|