Merge branch 'dev'

This commit is contained in:
Viacheslav Demydiuk
2026-04-07 11:37:39 +03:00
8 changed files with 56 additions and 8 deletions
+3 -1
View File
@@ -4,15 +4,17 @@ namespace MtApi5
{ {
public class Mt5TimeBarArgs: EventArgs public class Mt5TimeBarArgs: EventArgs
{ {
internal Mt5TimeBarArgs(int expertHandle, string symbol, MqlRates rates) internal Mt5TimeBarArgs(int expertHandle, string symbol, ENUM_TIMEFRAMES timeframe, MqlRates rates)
{ {
ExpertHandle = expertHandle; ExpertHandle = expertHandle;
Rates = rates; Rates = rates;
Symbol = symbol; Symbol = symbol;
Timeframe = timeframe;
} }
public int ExpertHandle { get; } public int ExpertHandle { get; }
public string Symbol { get; } public string Symbol { get; }
public ENUM_TIMEFRAMES Timeframe { get; }
public MqlRates Rates { get; } public MqlRates Rates { get; }
} }
} }
+15 -2
View File
@@ -32,11 +32,24 @@ namespace MtApi5
private HashSet<int> _experts = []; private HashSet<int> _experts = [];
private Dictionary<int, Mt5Quote> _quotes = []; private Dictionary<int, Mt5Quote> _quotes = [];
private volatile int _command_timeout = 30000; // 30 seconds
#endregion #endregion
#region Public Methods #region Public Methods
private IMtLogger Log { get; } private IMtLogger Log { get; }
// Time in milliseconds to wait for a response from MetaTrader for a command. Default is 30 seconds.
public int CommandTimeout
{
get => _command_timeout;
set
{
if (value <= 0)
throw new ArgumentException("Command timeout must be greater than zero.");
_command_timeout = value;
}
}
public MtApi5Client(IMtLogger? log = null) public MtApi5Client(IMtLogger? log = null)
{ {
_mtEventHandlers[Mt5EventTypes.OnBookEvent] = ReceivedOnBookEvent; _mtEventHandlers[Mt5EventTypes.OnBookEvent] = ReceivedOnBookEvent;
@@ -3533,7 +3546,7 @@ namespace MtApi5
var e = JsonConvert.DeserializeObject<OnLastTimeBarEvent>(payload); var e = JsonConvert.DeserializeObject<OnLastTimeBarEvent>(payload);
if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Rates == null) if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Rates == null)
return; return;
OnLastTimeBar?.Invoke(this, new Mt5TimeBarArgs(expertHandle, e.Instrument, e.Rates)); OnLastTimeBar?.Invoke(this, new Mt5TimeBarArgs(expertHandle, e.Instrument, e.Timeframe, e.Rates));
} }
private void ReceivedOnLockTicksEvent(int expertHandle, string payload) private void ReceivedOnLockTicksEvent(int expertHandle, string payload)
@@ -3588,7 +3601,7 @@ namespace MtApi5
var payloadJson = payload == null ? string.Empty : JsonConvert.SerializeObject(payload); var payloadJson = payload == null ? string.Empty : JsonConvert.SerializeObject(payload);
Log.Debug($"SendCommand: sending '{payloadJson}' ..."); Log.Debug($"SendCommand: sending '{payloadJson}' ...");
var responseJson = client.SendCommand(expertHandle, (int)commandType, payloadJson); var responseJson = client.SendCommand(expertHandle, (int)commandType, payloadJson, CommandTimeout);
Log.Debug($"SendCommand: received response JSON [{responseJson}]"); Log.Debug($"SendCommand: received response JSON [{responseJson}]");
+1
View File
@@ -4,6 +4,7 @@
{ {
public MqlRates? Rates { get; set; } public MqlRates? Rates { get; set; }
public string? Instrument { get; set; } public string? Instrument { get; set; }
public ENUM_TIMEFRAMES Timeframe { get; set; }
public int ExpertHandle { get; set; } public int ExpertHandle { get; set; }
} }
} }
+2 -2
View File
@@ -65,7 +65,7 @@ namespace MtClient
logger_.Debug($"MtRpcClient.Disconnect: success"); logger_.Debug($"MtRpcClient.Disconnect: success");
} }
public string? SendCommand(int expertHandle, int commandType, string payload) public string? SendCommand(int expertHandle, int commandType, string payload, int timeout = 10000) // 10 sec
{ {
CommandTask<string> commandTask = new(); CommandTask<string> commandTask = new();
int commandId; int commandId;
@@ -78,7 +78,7 @@ namespace MtClient
MtCommand command = new(expertHandle, commandType, commandId, payload); MtCommand command = new(expertHandle, commandType, commandId, payload);
Send(command); Send(command);
var response = commandTask.WaitResponse(10000); // 10 sec var response = commandTask.WaitResponse(timeout);
lock (tasks_) lock (tasks_)
{ {
tasks_.Remove(commandId); tasks_.Remove(commandId);
@@ -167,6 +167,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Row="0" Content="Quotes" Background="LightYellow" /> <Label Grid.Row="0" Content="Quotes" Background="LightYellow" />
<ListView Grid.Row="1" Margin="2" <ListView Grid.Row="1" Margin="2"
@@ -183,6 +184,11 @@
</ListView.View> </ListView.View>
</ListView> </ListView>
<Button Grid.Row="2" Content="Refresh Quotes" Margin="2" Command="{Binding RefreshQuotesCommand}"/> <Button Grid.Row="2" Content="Refresh Quotes" Margin="2" Command="{Binding RefreshQuotesCommand}"/>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Left" Margin="2">
<TextBlock Text="Command timeout (ms):" VerticalAlignment="Center" Margin="2"/>
<TextBox Text="{Binding CommandTimeout}" Width="100" Margin="2"/>
<Button Content="Apply" Margin="2" Command="{Binding TimeoutApplyCommand}"/>
</StackPanel>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
+23 -1
View File
@@ -163,6 +163,8 @@ namespace MtApi5TestClient
public DelegateCommand GetSymbolsCommand { get; private set; } public DelegateCommand GetSymbolsCommand { get; private set; }
public DelegateCommand RefreshQuotesCommand { get; private set; } public DelegateCommand RefreshQuotesCommand { get; private set; }
public DelegateCommand TimeoutApplyCommand { get; private set; }
#endregion #endregion
#region Properties #region Properties
@@ -210,6 +212,17 @@ namespace MtApi5TestClient
} }
} }
private int _command_timeout;
public int CommandTimeout
{
get { return _command_timeout; }
set
{
_command_timeout = value;
OnPropertyChanged("CommandTimeout");
}
}
public ObservableCollection<QuoteViewModel> Quotes { get; } = new ObservableCollection<QuoteViewModel>(); public ObservableCollection<QuoteViewModel> Quotes { get; } = new ObservableCollection<QuoteViewModel>();
private QuoteViewModel _selectedQuote; private QuoteViewModel _selectedQuote;
@@ -350,6 +363,7 @@ namespace MtApi5TestClient
ConnectionState = _mtApiClient.ConnectionState; ConnectionState = _mtApiClient.ConnectionState;
ConnectionMessage = "Disconnected"; ConnectionMessage = "Disconnected";
Port = 8228; //default local port Port = 8228; //default local port
CommandTimeout = _mtApiClient.CommandTimeout;
InitCommands(); InitCommands();
@@ -494,6 +508,8 @@ namespace MtApi5TestClient
GetSymbolsCommand = new DelegateCommand(ExecuteGetSymbols); GetSymbolsCommand = new DelegateCommand(ExecuteGetSymbols);
RefreshQuotesCommand = new DelegateCommand(ExecuteRefreshQuotes); RefreshQuotesCommand = new DelegateCommand(ExecuteRefreshQuotes);
TimeoutApplyCommand = new DelegateCommand(ExecuteTimeoutApply);
} }
private bool CanExecuteConnect(object o) private bool CanExecuteConnect(object o)
@@ -1773,6 +1789,12 @@ namespace MtApi5TestClient
} }
} }
private void ExecuteTimeoutApply(object o)
{
_mtApiClient.CommandTimeout = CommandTimeout;
AddLog($"TimeoutApply: timeout = {CommandTimeout} milliseconds");
}
private static void RunOnUiThread(Action action) private static void RunOnUiThread(Action action)
{ {
Application.Current?.Dispatcher.Invoke(action); Application.Current?.Dispatcher.Invoke(action);
@@ -1851,7 +1873,7 @@ namespace MtApi5TestClient
private void _mtApiClient_OnLastTimeBar(object sender, Mt5TimeBarArgs e) private void _mtApiClient_OnLastTimeBar(object sender, Mt5TimeBarArgs e)
{ {
AddLog($"OnLastTimeBarEvent: ExpertHandle = {e.ExpertHandle}, Symbol = {e.Symbol}, open = {e.Rates.open}, close = {e.Rates.close}, time = {e.Rates.time}, high = {e.Rates.high}, low = {e.Rates.low}"); AddLog($"OnLastTimeBarEvent: ExpertHandle = {e.ExpertHandle}, Symbol = {e.Symbol}, Timeframe = {e.Timeframe}, open = {e.Rates.open}, close = {e.Rates.close}, time = {e.Rates.time}, high = {e.Rates.high}, low = {e.Rates.low}");
} }
private void _mtApiClient_OnLockTicks(object sender, Mt5LockTicksEventArgs e) private void _mtApiClient_OnLockTicks(object sender, Mt5LockTicksEventArgs e)
BIN
View File
Binary file not shown.
+6 -2
View File
@@ -82,7 +82,7 @@ void OnTick()
MqlRates rates_array[]; MqlRates rates_array[];
CopyRates(symbol, Period(), 1, 1, rates_array); CopyRates(symbol, Period(), 1, 1, rates_array);
MtTimeBarEvent time_bar(symbol, rates_array[0]); MtTimeBarEvent time_bar(symbol, (int)Period(), rates_array[0]);
SendMtEvent(ON_LAST_TIME_BAR_EVENT, time_bar); SendMtEvent(ON_LAST_TIME_BAR_EVENT, time_bar);
} }
lastbar_time_changed = true; lastbar_time_changed = true;
@@ -2960,6 +2960,7 @@ string Execute_UnlockTicks()
return CreateErrorResponse(-1, "UnlockTicks can be used only for backtesting"); return CreateErrorResponse(-1, "UnlockTicks can be used only for backtesting");
} }
_is_ticks_locked = false;
return CreateSuccessResponse(); return CreateSuccessResponse();
} }
@@ -3662,9 +3663,10 @@ private:
class MtTimeBarEvent: public MtObject class MtTimeBarEvent: public MtObject
{ {
public: public:
MtTimeBarEvent(string symbol, const MqlRates& rates) MtTimeBarEvent(string symbol, int timeframe, const MqlRates& rates)
{ {
_symbol = symbol; _symbol = symbol;
_timeframe = timeframe;
_rates = rates; _rates = rates;
} }
@@ -3673,12 +3675,14 @@ public:
JSONObject *jo = new JSONObject(); JSONObject *jo = new JSONObject();
jo.put("Rates", MqlRatesToJson(_rates)); jo.put("Rates", MqlRatesToJson(_rates));
jo.put("Instrument", new JSONString(_symbol)); jo.put("Instrument", new JSONString(_symbol));
jo.put("Timeframe", new JSONNumber(_timeframe));
jo.put("ExpertHandle", new JSONNumber(ExpertHandle)); jo.put("ExpertHandle", new JSONNumber(ExpertHandle));
return jo; return jo;
} }
private: private:
string _symbol; string _symbol;
int _timeframe;
MqlRates _rates; MqlRates _rates;
}; };