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
2 changes: 2 additions & 0 deletions MtApi5/MqlTick.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal MqlTick(MtTick? tick)
if (tick != null)
{
MtTime = tick.Time;
time_msc = tick.TimeMsc;
bid = tick.Bid;
ask = tick.Ask;
last = tick.Last;
Expand All @@ -39,6 +40,7 @@ internal MqlTick(MtTick? tick)

public long MtTime { get; set; } // Time of the last prices update

public long time_msc { get; set; } // Time of the last prices update in milliseconds (0 = derive from MtTime)
public double bid { get; set; } // Current Bid price
public double ask { get; set; } // Current Ask price
public double last { get; set; } // Price of the last deal (Last)
Expand Down
205 changes: 205 additions & 0 deletions MtApi5/MtApi5Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,211 @@ public bool SymbolInfoTick(string symbol, out MqlTick? tick)
return null;
}

#region Custom Symbols

///<summary>
///Creates a custom symbol with the specified name in the specified group.
///</summary>
///<param name="symbolName">Custom symbol name. It must not contain groups or subgroups the symbol is located in.</param>
///<param name="symbolPath">The group name a symbol is located in.</param>
///<param name="symbolOrigin">Name of a symbol the properties of the created custom symbol are to be copied from.</param>
///<returns>true if successful, otherwise false.</returns>
public bool CustomSymbolCreate(string symbolName, string symbolPath = "", string symbolOrigin = "")
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "SymbolPath", symbolPath ?? string.Empty }, { "SymbolOrigin", symbolOrigin ?? string.Empty } };
return SendCommand<bool>(ExecutorHandle, Mt5CommandType.CustomSymbolCreate, cmdParams);
}

///<summary>
///Deletes a custom symbol with the specified name.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<returns>true if successful, otherwise false.</returns>
public bool CustomSymbolDelete(string symbolName)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty } };
return SendCommand<bool>(ExecutorHandle, Mt5CommandType.CustomSymbolDelete, cmdParams);
}

///<summary>
///Sets the double type property value for a custom symbol.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="propertyId">Symbol property ID.</param>
///<param name="value">A double type property value.</param>
///<returns>true if successful, otherwise false.</returns>
public bool CustomSymbolSetDouble(string symbolName, ENUM_SYMBOL_INFO_DOUBLE propertyId, double value)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "PropertyId", (int)propertyId }, { "PropertyValue", value } };
return SendCommand<bool>(ExecutorHandle, Mt5CommandType.CustomSymbolSetDouble, cmdParams);
}

///<summary>
///Sets the integer type property value for a custom symbol.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="propertyId">Symbol property ID.</param>
///<param name="value">A long type property value.</param>
///<returns>true if successful, otherwise false.</returns>
public bool CustomSymbolSetInteger(string symbolName, ENUM_SYMBOL_INFO_INTEGER propertyId, long value)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "PropertyId", (int)propertyId }, { "PropertyValue", value } };
return SendCommand<bool>(ExecutorHandle, Mt5CommandType.CustomSymbolSetInteger, cmdParams);
}

///<summary>
///Sets the string type property value for a custom symbol.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="propertyId">Symbol property ID.</param>
///<param name="value">A string type property value.</param>
///<returns>true if successful, otherwise false.</returns>
public bool CustomSymbolSetString(string symbolName, ENUM_SYMBOL_INFO_STRING propertyId, string value)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "PropertyId", (int)propertyId }, { "PropertyValue", value ?? string.Empty } };
return SendCommand<bool>(ExecutorHandle, Mt5CommandType.CustomSymbolSetString, cmdParams);
}

///<summary>
///Deletes all bars from the price history of the custom symbol in the specified time interval.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="from">Time of the first bar in the price history within the specified range to be removed.</param>
///<param name="to">Time of the last bar in the price history within the specified range to be removed.</param>
///<returns>Number of deleted bars or -1 in case of an error.</returns>
public int CustomRatesDelete(string symbolName, DateTime from, DateTime to)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "FromDate", Mt5TimeConverter.ConvertToMtTime(from) }, { "ToDate", Mt5TimeConverter.ConvertToMtTime(to) } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomRatesDelete, cmdParams);
}

///<summary>
///Fully replaces the price history of the custom symbol within the specified time interval with the data from the MqlRates type array.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="from">Time of the first bar in the price history within the specified range to be updated.</param>
///<param name="to">Time of the last bar in the price history within the specified range to be updated.</param>
///<param name="rates">Array of the MqlRates type history data for M1.</param>
///<returns>Number of updated bars or -1 in case of an error.</returns>
public int CustomRatesReplace(string symbolName, DateTime from, DateTime to, MqlRates[] rates)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "FromDate", Mt5TimeConverter.ConvertToMtTime(from) }, { "ToDate", Mt5TimeConverter.ConvertToMtTime(to) },
{ "Rates", MqlRatesArrayToPayload(rates) } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomRatesReplace, cmdParams);
}

///<summary>
///Adds missing bars to the custom symbol history and replaces existing data with the ones from the MqlRates type array.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="rates">Array of the MqlRates type history data for M1.</param>
///<returns>Number of updated bars or -1 in case of an error.</returns>
public int CustomRatesUpdate(string symbolName, MqlRates[] rates)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "Rates", MqlRatesArrayToPayload(rates) } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomRatesUpdate, cmdParams);
}

///<summary>
///Adds data from an array of the MqlTick type to the price history of a custom symbol. The custom symbol must be selected in the Market Watch window.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="ticks">Array of the MqlTick type tick data arranged in order of time from the oldest to the newest.</param>
///<returns>Number of added ticks or -1 in case of an error.</returns>
public int CustomTicksAdd(string symbolName, MqlTick[] ticks)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "Ticks", MqlTickArrayToPayload(ticks) } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomTicksAdd, cmdParams);
}

///<summary>
///Deletes all ticks from the price history of the custom symbol in the specified time interval.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="fromMsc">Time of the first tick in the price history within the specified range to be removed, in milliseconds since 1970.01.01.</param>
///<param name="toMsc">Time of the last tick in the price history within the specified range to be removed, in milliseconds since 1970.01.01.</param>
///<returns>Number of deleted ticks or -1 in case of an error.</returns>
public int CustomTicksDelete(string symbolName, long fromMsc, long toMsc)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "FromMsc", fromMsc }, { "ToMsc", toMsc } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomTicksDelete, cmdParams);
}

///<summary>
///Fully replaces the price history of the custom symbol within the specified time interval with the data from the MqlTick type array.
///</summary>
///<param name="symbolName">Custom symbol name.</param>
///<param name="fromMsc">Time of the first tick in the price history within the specified range to be replaced, in milliseconds since 1970.01.01.</param>
///<param name="toMsc">Time of the last tick in the price history within the specified range to be replaced, in milliseconds since 1970.01.01.</param>
///<param name="ticks">Array of the MqlTick type tick data arranged in order of time from the oldest to the newest.</param>
///<returns>Number of replaced ticks or -1 in case of an error.</returns>
public int CustomTicksReplace(string symbolName, long fromMsc, long toMsc, MqlTick[] ticks)
{
Dictionary<string, object> cmdParams = new() { { "SymbolName", symbolName ?? string.Empty },
{ "FromMsc", fromMsc }, { "ToMsc", toMsc }, { "Ticks", MqlTickArrayToPayload(ticks) } };
return SendCommand<int>(ExecutorHandle, Mt5CommandType.CustomTicksReplace, cmdParams);
}

// Serializes MqlRates[] for the command payload using the same field names
// the EA uses in MqlRatesToJson (the EA parses the reverse direction).
private static List<Dictionary<string, object>> MqlRatesArrayToPayload(MqlRates[]? rates)
{
List<Dictionary<string, object>> payload = new(rates?.Length ?? 0);
if (rates == null)
return payload;
foreach (var r in rates)
{
payload.Add(new Dictionary<string, object>
{
{ "mt_time", r.mt_time },
{ "open", r.open },
{ "high", r.high },
{ "low", r.low },
{ "close", r.close },
{ "tick_volume", r.tick_volume },
{ "spread", r.spread },
{ "real_volume", r.real_volume }
});
}
return payload;
}

// Serializes MqlTick[] for the command payload using the same field names
// the EA uses in MqlTickToJson (the EA parses the reverse direction).
// TimeMsc falls back to MtTime * 1000 when time_msc is not set.
private static List<Dictionary<string, object>> MqlTickArrayToPayload(MqlTick[]? ticks)
{
List<Dictionary<string, object>> payload = new(ticks?.Length ?? 0);
if (ticks == null)
return payload;
foreach (var t in ticks)
{
payload.Add(new Dictionary<string, object>
{
{ "Time", t.MtTime },
{ "TimeMsc", t.time_msc != 0 ? t.time_msc : t.MtTime * 1000 },
{ "Bid", t.bid },
{ "Ask", t.ask },
{ "Last", t.last },
{ "Volume", t.volume },
{ "VolumeReal", t.volume_real },
{ "Flags", (uint)t.flags }
});
}
return payload;
}

#endregion

///<summary>
///Allows receiving time of beginning and end of the specified quoting sessions for a specified symbol and weekday.
///</summary>
Expand Down
1 change: 1 addition & 0 deletions MtApi5/MtProtocol/MqlTick.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class MtTick
public double Bid { get; set; } // Current Bid price
public double Ask { get; set; } // Current Ask price
public long Time { get; set; } // Time of the last prices update
public long TimeMsc { get; set; } // Time of the last prices update in milliseconds
public double Last { get; set; } // Price of the last deal (Last)
public ulong Volume { get; set; } // Volume for the current Last price
public double VolumeReal { get; set; } // Volume for the current Last price with greater accuracy
Expand Down
14 changes: 13 additions & 1 deletion MtApi5/MtProtocol/Mt5CommandType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ internal enum Mt5CommandType
OrderCheck = 303,
Buy = 304,
Sell = 305,
GetSymbols = 306
GetSymbols = 306,

CustomSymbolCreate = 340,
CustomSymbolDelete = 341,
CustomSymbolSetDouble = 342,
CustomSymbolSetInteger = 343,
CustomSymbolSetString = 344,
CustomRatesDelete = 345,
CustomRatesReplace = 346,
CustomRatesUpdate = 347,
CustomTicksAdd = 348,
CustomTicksDelete = 349,
CustomTicksReplace = 350
}
}
2 changes: 2 additions & 0 deletions TestClients/MtApi5TestClient/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@
<WrapPanel Grid.Row="1" Grid.Column="0" Margin="5">
<Button Command="{Binding CopyRatesCommand}" Margin="1"
Content="CopyRates" HorizontalAlignment="Left" />
<Button Command="{Binding CustomSymbolTestCommand}" Margin="1"
Content="CustomSymbolTest" HorizontalAlignment="Left" />
<Button Command="{Binding CopyTimesCommand}" Margin="1"
Content="CopyTimes" HorizontalAlignment="Left" />
<Button Command="{Binding CopyOpenCommand}" Margin="1"
Expand Down
44 changes: 44 additions & 0 deletions TestClients/MtApi5TestClient/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class ViewModel : INotifyPropertyChanged
public DelegateCommand TerminalInfoStringCommand { get; private set; }

public DelegateCommand CopyRatesCommand { get; private set; }
public DelegateCommand CustomSymbolTestCommand { get; private set; }
public DelegateCommand CopyTimesCommand { get; private set; }
public DelegateCommand CopyOpenCommand { get; private set; }
public DelegateCommand CopyHighCommand { get; private set; }
Expand Down Expand Up @@ -412,6 +413,7 @@ private void InitCommands()
TerminalInfoStringCommand = new DelegateCommand(ExecuteTerminalInfoString);

CopyRatesCommand = new DelegateCommand(ExecuteCopyRates);
CustomSymbolTestCommand = new DelegateCommand(ExecuteCustomSymbolTest);
CopyTimesCommand = new DelegateCommand(ExecuteCopyTime);
CopyOpenCommand = new DelegateCommand(ExecuteCopyOpen);
CopyHighCommand = new DelegateCommand(ExecuteCopyHigh);
Expand Down Expand Up @@ -1939,6 +1941,48 @@ private void OnSelectedQuoteChanged()
TimeSeriesValues.SymbolValue = SelectedQuote.Instrument;
}

private async void ExecuteCustomSymbolTest(object o)
{
const string symbol = "MtApi5.Custom";

await Execute(() =>
{
var created = _mtApiClient.CustomSymbolCreate(symbol, "Custom");
AddLog($"CustomSymbolCreate({symbol}): {created}");

AddLog($"CustomSymbolSetInteger(SYMBOL_DIGITS, 5): {_mtApiClient.CustomSymbolSetInteger(symbol, ENUM_SYMBOL_INFO_INTEGER.SYMBOL_DIGITS, 5)}");
AddLog($"CustomSymbolSetDouble(SYMBOL_POINT, 0.00001): {_mtApiClient.CustomSymbolSetDouble(symbol, ENUM_SYMBOL_INFO_DOUBLE.SYMBOL_POINT, 0.00001)}");
AddLog($"CustomSymbolSetString(SYMBOL_DESCRIPTION): {_mtApiClient.CustomSymbolSetString(symbol, ENUM_SYMBOL_INFO_STRING.SYMBOL_DESCRIPTION, "MtApi5 custom symbol test")}");

var minute = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day,
DateTime.UtcNow.Hour, DateTime.UtcNow.Minute, 0);
var rates = new MqlRates[3];
for (var i = 0; i < rates.Length; i++)
{
var open = 1.10000 + i * 0.00010;
rates[i] = new MqlRates(minute.AddMinutes(i - rates.Length), open, open + 0.00020,
open - 0.00020, open + 0.00010, 10 + i, 2, 0);
}
AddLog($"CustomRatesUpdate: {_mtApiClient.CustomRatesUpdate(symbol, rates)}");

var ticks = new MqlTick[2];
for (var i = 0; i < ticks.Length; i++)
{
ticks[i] = new MqlTick(minute.AddSeconds(i), 1.10030 + i * 0.00001, 1.10050 + i * 0.00001, 0, 0,
ENUM_TICK_FLAGS.TICK_FLAG_BID | ENUM_TICK_FLAGS.TICK_FLAG_ASK);
}
AddLog($"SymbolSelect({symbol}): {_mtApiClient.SymbolSelect(symbol, true)}");
AddLog($"CustomTicksAdd: {_mtApiClient.CustomTicksAdd(symbol, ticks)}");

AddLog($"CustomTicksDelete: {_mtApiClient.CustomTicksDelete(symbol, ticks[0].MtTime * 1000, (ticks[1].MtTime + 1) * 1000)}");
AddLog($"CustomRatesDelete: {_mtApiClient.CustomRatesDelete(symbol, rates[0].time, rates[rates.Length - 1].time)}");

AddLog($"SymbolSelect({symbol}, false): {_mtApiClient.SymbolSelect(symbol, false)}");
AddLog($"CustomSymbolDelete({symbol}): {_mtApiClient.CustomSymbolDelete(symbol)}");
return true;
});
}

private async Task<TResult> Execute<TResult>(Func<TResult> func)
{
return await Task.Factory.StartNew(() =>
Expand Down
Binary file modified mq5/MtApi5.ex5
Binary file not shown.
Loading