Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions MtApi5/Mt5Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;

namespace MtApi5
{
///<summary>
///Snapshot of a current (pending) order with all its properties.
///Returned by MtApi5Client.GetOrders() in a single request instead of the
///OrdersTotal/OrderGetTicket/OrderGet* per-property call sequence.
///</summary>
public class Mt5Order
{
///<summary>Order ticket (ORDER_TICKET). Unique number assigned to each order.</summary>
public ulong Ticket { get; set; }

///<summary>Symbol of the order (ORDER_SYMBOL).</summary>
public string Symbol { get; set; } = string.Empty;

///<summary>Order setup time (ORDER_TIME_SETUP).</summary>
public DateTime TimeSetup => Mt5TimeConverter.ConvertFromMtTime(MtTimeSetup);

///<summary>The time of placing the order in milliseconds since 01.01.1970 (ORDER_TIME_SETUP_MSC).</summary>
public long TimeSetupMsc { get; set; }

///<summary>Order expiration time (ORDER_TIME_EXPIRATION).</summary>
public DateTime TimeExpiration => Mt5TimeConverter.ConvertFromMtTime(MtTimeExpiration);

///<summary>Order type (ORDER_TYPE).</summary>
public ENUM_ORDER_TYPE Type { get; set; }

///<summary>Order lifetime (ORDER_TYPE_TIME).</summary>
public ENUM_ORDER_TYPE_TIME TypeTime { get; set; }

///<summary>Order filling type (ORDER_TYPE_FILLING).</summary>
public ENUM_ORDER_TYPE_FILLING TypeFilling { get; set; }

///<summary>Order state (ORDER_STATE).</summary>
public ENUM_ORDER_STATE State { get; set; }

///<summary>Order magic number (ORDER_MAGIC).</summary>
public long Magic { get; set; }

///<summary>Position identifier that is set to an order as soon as it is executed (ORDER_POSITION_ID).</summary>
public long PositionId { get; set; }

///<summary>Identifier of an opposite position used for closing by order (ORDER_POSITION_BY_ID).</summary>
public long PositionById { get; set; }

///<summary>Order initial volume (ORDER_VOLUME_INITIAL).</summary>
public double VolumeInitial { get; set; }

///<summary>Order current volume (ORDER_VOLUME_CURRENT).</summary>
public double VolumeCurrent { get; set; }

///<summary>Price specified in the order (ORDER_PRICE_OPEN).</summary>
public double PriceOpen { get; set; }

///<summary>Stop Loss value (ORDER_SL).</summary>
public double StopLoss { get; set; }

///<summary>Take Profit value (ORDER_TP).</summary>
public double TakeProfit { get; set; }

///<summary>The current price of the order symbol (ORDER_PRICE_CURRENT).</summary>
public double PriceCurrent { get; set; }

///<summary>The limit order price for the StopLimit order (ORDER_PRICE_STOPLIMIT).</summary>
public double PriceStopLimit { get; set; }

///<summary>Order comment (ORDER_COMMENT).</summary>
public string Comment { get; set; } = string.Empty;

///<summary>Order identifier in an external trading system (ORDER_EXTERNAL_ID).</summary>
public string ExternalId { get; set; } = string.Empty;

///<summary>The reason for order placing (ORDER_REASON).</summary>
public ENUM_ORDER_REASON Reason { get; set; }

///<summary>Order setup time as seconds since 01.01.1970 (raw MT time).</summary>
public long MtTimeSetup { get; set; }

///<summary>Order expiration time as seconds since 01.01.1970 (raw MT time).</summary>
public long MtTimeExpiration { get; set; }

public override string ToString()
{
return $"Ticket={Ticket}; Symbol={Symbol}; TimeSetup={TimeSetup}; Type={Type}; State={State}; TypeTime={TypeTime}; TypeFilling={TypeFilling}; TimeExpiration={TimeExpiration}; VolumeInitial={VolumeInitial}; VolumeCurrent={VolumeCurrent}; PriceOpen={PriceOpen}; StopLoss={StopLoss}; TakeProfit={TakeProfit}; PriceCurrent={PriceCurrent}; PriceStopLimit={PriceStopLimit}; Magic={Magic}; PositionId={PositionId}; PositionById={PositionById}; Reason={Reason}; Comment={Comment}; ExternalId={ExternalId}";
}
}
}
77 changes: 77 additions & 0 deletions MtApi5/Mt5Position.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;

namespace MtApi5
{
///<summary>
///Snapshot of an open position with all its properties.
///Returned by MtApi5Client.GetPositions() in a single request instead of the
///PositionsTotal/PositionGetTicket/PositionGet* per-property call sequence.
///</summary>
public class Mt5Position
{
///<summary>Position ticket (POSITION_TICKET). Unique number assigned to each newly opened position.</summary>
public ulong Ticket { get; set; }

///<summary>Symbol of the position (POSITION_SYMBOL).</summary>
public string Symbol { get; set; } = string.Empty;

///<summary>Position open time (POSITION_TIME).</summary>
public DateTime Time => Mt5TimeConverter.ConvertFromMtTime(MtTime);

///<summary>Position open time in milliseconds since 01.01.1970 (POSITION_TIME_MSC).</summary>
public long TimeMsc { get; set; }

///<summary>Position changing time (POSITION_TIME_UPDATE).</summary>
public DateTime TimeUpdate => Mt5TimeConverter.ConvertFromMtTime(MtTimeUpdate);

///<summary>Position type (POSITION_TYPE).</summary>
public ENUM_POSITION_TYPE Type { get; set; }

///<summary>Position magic number (POSITION_MAGIC).</summary>
public long Magic { get; set; }

///<summary>Position identifier (POSITION_IDENTIFIER). Doesn't change during the entire lifetime of the position.</summary>
public long Identifier { get; set; }

///<summary>The reason for opening the position (POSITION_REASON).</summary>
public ENUM_POSITION_REASON Reason { get; set; }

///<summary>Position volume (POSITION_VOLUME).</summary>
public double Volume { get; set; }

///<summary>Position open price (POSITION_PRICE_OPEN).</summary>
public double PriceOpen { get; set; }

///<summary>Stop Loss level of the opened position (POSITION_SL).</summary>
public double StopLoss { get; set; }

///<summary>Take Profit level of the opened position (POSITION_TP).</summary>
public double TakeProfit { get; set; }

///<summary>Current price of the position symbol (POSITION_PRICE_CURRENT).</summary>
public double PriceCurrent { get; set; }

///<summary>Cumulative swap (POSITION_SWAP).</summary>
public double Swap { get; set; }

///<summary>Current profit (POSITION_PROFIT).</summary>
public double Profit { get; set; }

///<summary>Position comment (POSITION_COMMENT).</summary>
public string Comment { get; set; } = string.Empty;

///<summary>Position identifier in an external trading system (POSITION_EXTERNAL_ID).</summary>
public string ExternalId { get; set; } = string.Empty;

///<summary>Position open time as seconds since 01.01.1970 (raw MT time).</summary>
public long MtTime { get; set; }

///<summary>Position changing time as seconds since 01.01.1970 (raw MT time).</summary>
public long MtTimeUpdate { get; set; }

public override string ToString()
{
return $"Ticket={Ticket}; Symbol={Symbol}; Time={Time}; Type={Type}; Volume={Volume}; PriceOpen={PriceOpen}; StopLoss={StopLoss}; TakeProfit={TakeProfit}; PriceCurrent={PriceCurrent}; Swap={Swap}; Profit={Profit}; Magic={Magic}; Identifier={Identifier}; Reason={Reason}; Comment={Comment}; ExternalId={ExternalId}";
}
}
}
22 changes: 22 additions & 0 deletions MtApi5/MtApi5Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,17 @@ public ulong PositionGetTicket(int index)
return SendCommand<ulong>(ExecutorHandle, Mt5CommandType.PositionGetTicket, cmdParams);
}

///<summary>
///Gets a snapshot of all open positions with all their properties in a single request.
///Replaces the PositionsTotal/PositionGetTicket/PositionGet* per-property call sequence
///with one round-trip to the terminal.
///</summary>
///<returns>List of open positions (empty if there are none) or null if the request failed.</returns>
public List<Mt5Position>? GetPositions()
{
return SendCommand<List<Mt5Position>>(ExecutorHandle, Mt5CommandType.GetAllPositions);
}

///<summary>
///Returns the number of current orders.
///</summary>
Expand Down Expand Up @@ -510,6 +521,17 @@ public long OrderGetInteger(ENUM_ORDER_PROPERTY_INTEGER propertyId)
return SendCommand<string>(ExecutorHandle, Mt5CommandType.OrderGetString, cmdParams);
}

///<summary>
///Gets a snapshot of all current (pending) orders with all their properties in a single request.
///Replaces the OrdersTotal/OrderGetTicket/OrderGet* per-property call sequence
///with one round-trip to the terminal.
///</summary>
///<returns>List of current orders (empty if there are none) or null if the request failed.</returns>
public List<Mt5Order>? GetOrders()
{
return SendCommand<List<Mt5Order>>(ExecutorHandle, Mt5CommandType.GetAllOrders);
}

///<summary>
///Retrieves the history of deals and orders for the specified period of server time.
///</summary>
Expand Down
5 changes: 4 additions & 1 deletion MtApi5/MtProtocol/Mt5CommandType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ internal enum Mt5CommandType
OrderCheck = 303,
Buy = 304,
Sell = 305,
GetSymbols = 306
GetSymbols = 306,

GetAllPositions = 390,
GetAllOrders = 391
}
}
1 change: 1 addition & 0 deletions TestClients/MtApi5TestClient/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
<Button Content="OrderSend" Command="{Binding OrderSendCommand}" Width="100" Height="25" HorizontalAlignment="Left" Margin="4"/>
<Button Content="OrderCheck" Command="{Binding OrderCheckCommand}" Width="100" Height="25" HorizontalAlignment="Left" Margin="4"/>
<Button Content="PositionGetTicket" Command="{Binding PositionGetTicketCommand}" Width="100" Height="25" HorizontalAlignment="Left" Margin="4"/>
<Button Content="GetPositionsOrders" Command="{Binding GetPositionsOrdersCommand}" Width="120" Height="25" HorizontalAlignment="Left" Margin="4"/>
</StackPanel>

<WrapPanel>
Expand Down
19 changes: 19 additions & 0 deletions TestClients/MtApi5TestClient/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class ViewModel : INotifyPropertyChanged
public DelegateCommand OrderSendCommand { get; private set; }
public DelegateCommand OrderCheckCommand { get; private set; }
public DelegateCommand PositionGetTicketCommand { get; private set; }
public DelegateCommand GetPositionsOrdersCommand { get; private set; }

public DelegateCommand HistoryOrderGetIntegerCommand { get; private set; }
public DelegateCommand HistoryDealGetDoubleCommand { get; private set; }
Expand Down Expand Up @@ -396,6 +397,7 @@ private void InitCommands()
OrderSendCommand = new DelegateCommand(ExecuteOrderSend);
OrderCheckCommand = new DelegateCommand(ExecuteOrderCheck);
PositionGetTicketCommand = new DelegateCommand(ExecutePositionGetTicket);
GetPositionsOrdersCommand = new DelegateCommand(ExecuteGetPositionsOrders);

HistoryOrderGetIntegerCommand = new DelegateCommand(ExecuteHistoryOrderGetInteger);
HistoryDealGetDoubleCommand = new DelegateCommand(ExecuteHistoryDealGetDouble);
Expand Down Expand Up @@ -581,6 +583,23 @@ private async void ExecutePositionGetTicket(object obj)
AddLog(message);
}

private async void ExecuteGetPositionsOrders(object obj)
{
var positions = await Execute(() => _mtApiClient.GetPositions());
AddLog($"GetPositions: count = {positions?.Count ?? 0}");
if (positions != null && positions.Count > 0)
{
AddLog($" first: {positions[0]}");
}

var orders = await Execute(() => _mtApiClient.GetOrders());
AddLog($"GetOrders: count = {orders?.Count ?? 0}");
if (orders != null && orders.Count > 0)
{
AddLog($" first: {orders[0]}");
}
}

private async void ExecuteHistoryOrderGetInteger(object o)
{
const ulong ticket = 12345;
Expand Down
Binary file modified mq5/MtApi5.ex5
Binary file not shown.
101 changes: 100 additions & 1 deletion mq5/MtApi5.mq5
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,10 @@ int preinit()
ADD_EXECUTOR(304, Buy);
ADD_EXECUTOR(305, Sell);
ADD_EXECUTOR(306, GetSymbols);


ADD_EXECUTOR(390, GetAllPositions);
ADD_EXECUTOR(391, GetAllOrders);

return (0);
}

Expand Down Expand Up @@ -3474,6 +3477,102 @@ string Execute_GetSymbols()
return CreateSuccessResponse(jaSymbols);
}

//+------------------------------------------------------------------+
//| Position/order snapshot command handlers |
//+------------------------------------------------------------------+

// JSONString::toString() in json.mqh performs no escaping, so escape broker
// provided strings manually (backslash first, then quote and control
// characters) to keep the response JSON valid.
string EscapeJsonString(string value)
{
string escaped = value;
StringReplace(escaped, "\\", "\\\\");
StringReplace(escaped, "\"", "\\\"");
StringReplace(escaped, "\n", "\\n");
StringReplace(escaped, "\r", "\\r");
StringReplace(escaped, "\t", "\\t");
return escaped;
}

string Execute_GetAllPositions()
{
int total = PositionsTotal();
JSONArray* jaPositions = new JSONArray();
int idx = 0;
for(int i = 0; i < total; i++)
{
ulong ticket = PositionGetTicket(i); // also selects the position
if (ticket == 0)
continue; // the position was closed between PositionsTotal and selection

JSONObject* jo = new JSONObject();
jo.put("Ticket", new JSONNumber((long)ticket));
jo.put("Symbol", new JSONString(EscapeJsonString(PositionGetString(POSITION_SYMBOL))));
jo.put("MtTime", new JSONNumber(PositionGetInteger(POSITION_TIME)));
jo.put("TimeMsc", new JSONNumber(PositionGetInteger(POSITION_TIME_MSC)));
jo.put("MtTimeUpdate", new JSONNumber(PositionGetInteger(POSITION_TIME_UPDATE)));
jo.put("Type", new JSONNumber((int)PositionGetInteger(POSITION_TYPE)));
jo.put("Magic", new JSONNumber(PositionGetInteger(POSITION_MAGIC)));
jo.put("Identifier", new JSONNumber(PositionGetInteger(POSITION_IDENTIFIER)));
jo.put("Reason", new JSONNumber((int)PositionGetInteger(POSITION_REASON)));
jo.put("Volume", new JSONNumber(PositionGetDouble(POSITION_VOLUME)));
jo.put("PriceOpen", new JSONNumber(PositionGetDouble(POSITION_PRICE_OPEN)));
jo.put("StopLoss", new JSONNumber(PositionGetDouble(POSITION_SL)));
jo.put("TakeProfit", new JSONNumber(PositionGetDouble(POSITION_TP)));
jo.put("PriceCurrent", new JSONNumber(PositionGetDouble(POSITION_PRICE_CURRENT)));
jo.put("Swap", new JSONNumber(PositionGetDouble(POSITION_SWAP)));
jo.put("Profit", new JSONNumber(PositionGetDouble(POSITION_PROFIT)));
jo.put("Comment", new JSONString(EscapeJsonString(PositionGetString(POSITION_COMMENT))));
jo.put("ExternalId", new JSONString(EscapeJsonString(PositionGetString(POSITION_EXTERNAL_ID))));
jaPositions.put(idx, jo);
idx++;
}

return CreateSuccessResponse(jaPositions);
}

string Execute_GetAllOrders()
{
int total = OrdersTotal();
JSONArray* jaOrders = new JSONArray();
int idx = 0;
for(int i = 0; i < total; i++)
{
ulong ticket = OrderGetTicket(i); // also selects the order
if (ticket == 0)
continue; // the order was executed or removed between OrdersTotal and selection

JSONObject* jo = new JSONObject();
jo.put("Ticket", new JSONNumber((long)ticket));
jo.put("Symbol", new JSONString(EscapeJsonString(OrderGetString(ORDER_SYMBOL))));
jo.put("MtTimeSetup", new JSONNumber(OrderGetInteger(ORDER_TIME_SETUP)));
jo.put("TimeSetupMsc", new JSONNumber(OrderGetInteger(ORDER_TIME_SETUP_MSC)));
jo.put("MtTimeExpiration", new JSONNumber(OrderGetInteger(ORDER_TIME_EXPIRATION)));
jo.put("Type", new JSONNumber((int)OrderGetInteger(ORDER_TYPE)));
jo.put("TypeTime", new JSONNumber((int)OrderGetInteger(ORDER_TYPE_TIME)));
jo.put("TypeFilling", new JSONNumber((int)OrderGetInteger(ORDER_TYPE_FILLING)));
jo.put("State", new JSONNumber((int)OrderGetInteger(ORDER_STATE)));
jo.put("Magic", new JSONNumber(OrderGetInteger(ORDER_MAGIC)));
jo.put("PositionId", new JSONNumber(OrderGetInteger(ORDER_POSITION_ID)));
jo.put("PositionById", new JSONNumber(OrderGetInteger(ORDER_POSITION_BY_ID)));
jo.put("VolumeInitial", new JSONNumber(OrderGetDouble(ORDER_VOLUME_INITIAL)));
jo.put("VolumeCurrent", new JSONNumber(OrderGetDouble(ORDER_VOLUME_CURRENT)));
jo.put("PriceOpen", new JSONNumber(OrderGetDouble(ORDER_PRICE_OPEN)));
jo.put("StopLoss", new JSONNumber(OrderGetDouble(ORDER_SL)));
jo.put("TakeProfit", new JSONNumber(OrderGetDouble(ORDER_TP)));
jo.put("PriceCurrent", new JSONNumber(OrderGetDouble(ORDER_PRICE_CURRENT)));
jo.put("PriceStopLimit", new JSONNumber(OrderGetDouble(ORDER_PRICE_STOPLIMIT)));
jo.put("Comment", new JSONString(EscapeJsonString(OrderGetString(ORDER_COMMENT))));
jo.put("ExternalId", new JSONString(EscapeJsonString(OrderGetString(ORDER_EXTERNAL_ID))));
jo.put("Reason", new JSONNumber((int)OrderGetInteger(ORDER_REASON)));
jaOrders.put(idx, jo);
idx++;
}

return CreateSuccessResponse(jaOrders);
}

int PositionCloseAll()
{
CTrade trade;
Expand Down