Issue #122: Implemented UnlockTicks function in MtApi (MT5)

This commit is contained in:
vdemydiuk
2018-06-05 19:19:35 +03:00
parent 0f717f1cb7
commit 1a5882b75a
14 changed files with 252 additions and 30 deletions
+41 -12
View File
@@ -4,35 +4,51 @@ namespace MTApiService
{
public class Mt5Expert : MtExpert
{
private static readonly ILog Log = LogManager.GetLogger(typeof(MtExpert));
private static readonly ILog Log = LogManager.GetLogger(typeof(Mt5Expert));
private const int StopExpertInterval = 2000; // 2 sec for testing mode
private readonly System.Timers.Timer _stopTimer = new System.Timers.Timer();
private System.Timers.Timer _stopTimer;
public Mt5Expert(int handle, string symbol, double bid, double ask, IMetaTraderHandler mtHandler, bool isTestMode) :
base(handle, symbol, bid, ask, mtHandler)
{
IsTestMode = isTestMode;
_stopTimer.Interval = StopExpertInterval;
_stopTimer.Elapsed += _stopTimer_Elapsed;
}
public bool IsTestMode { get; }
public override void UpdateQuote(MtQuote quote)
public override int GetCommandType()
{
Log.Debug("UpdateQuote: begin.");
base.UpdateQuote(quote);
Log.Debug("GetCommandType: called.");
if (IsTestMode)
{
//reset timer
_stopTimer.Stop();
_stopTimer.Start();
ResetTestModeTimer();
}
Log.Debug("UpdateQuote: end.");
return base.GetCommandType();
}
public override void SendEvent(MtEvent mtEvent)
{
Log.DebugFormat("SendEvent: begin. event = {0}", mtEvent);
if (IsTestMode)
{
if (_stopTimer == null)
{
_stopTimer = new System.Timers.Timer
{
Interval = StopExpertInterval,
AutoReset = false
};
_stopTimer.Elapsed += _stopTimer_Elapsed;
}
}
base.SendEvent(mtEvent);
Log.Debug("SendEvent: end.");
}
private void _stopTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
@@ -42,7 +58,20 @@ namespace MTApiService
Log.Warn("Mt5Expert has received new tick during 2 sec in testing mode. The possible cause: user has stopped the tester manually in MetaTrader 5.");
Deinit();
_stopTimer.Elapsed -= _stopTimer_Elapsed;
_stopTimer = null;
Log.Debug("_stopTimer_Elapsed: end.");
}
private void ResetTestModeTimer()
{
if (_stopTimer == null)
return;
//reset timer
_stopTimer.Stop();
_stopTimer.Start();
}
}
}
+26 -1
View File
@@ -66,6 +66,7 @@ namespace MTApiService
}
server.AddExpert(expert);
expert.Deinited += ExpertOnDeinited;
Log.Info("AddExpert: end");
}
@@ -81,7 +82,6 @@ namespace MTApiService
if (_experts.ContainsKey(expertHandle))
{
expert = _experts[expertHandle];
_experts.Remove(expertHandle);
}
}
@@ -270,6 +270,31 @@ namespace MTApiService
}
}
}
private void ExpertOnDeinited(object sender, EventArgs eventArgs)
{
Log.Debug("ExpertOnDeinited: begin.");
var expert = sender as MtExpert;
if (expert == null)
{
Log.Warn("expert_Deinited: end. Expert is not defined.");
return;
}
lock (_experts)
{
if (_experts.ContainsKey(expert.Handle))
{
_experts.Remove(expert.Handle);
}
}
Log.DebugFormat("ExpertOnDeinited: removed expert {0}", expert.Handle);
Log.Debug("ExpertOnDeinited: end.");
}
#endregion
}
}
+3 -3
View File
@@ -48,7 +48,7 @@ namespace MTApiService
Log.Debug("SendResponse: end.");
}
public int GetCommandType()
public virtual int GetCommandType()
{
Log.Debug("GetCommandType: called.");
@@ -110,7 +110,7 @@ namespace MTApiService
return command.NamedParams.ContainsKey(name);
}
public void SendEvent(MtEvent mtEvent)
public virtual void SendEvent(MtEvent mtEvent)
{
Log.DebugFormat("SendEvent: begin. event = {0}", mtEvent);
@@ -119,7 +119,7 @@ namespace MTApiService
Log.Debug("SendEvent: end.");
}
public virtual void UpdateQuote(MtQuote quote)
public void UpdateQuote(MtQuote quote)
{
Log.DebugFormat("UpdateQuote: begin. quote = {0}", quote);
+2 -1
View File
@@ -4,6 +4,7 @@
{
OnTradeTransaction = 1,
OnBookEvent = 2,
OnTick = 3
OnTick = 3,
OnLastTimeBar = 4
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace MtApi5.Events
{
public class OnLastTimeBarEvent
{
public MqlRates Rates { get; set; }
public string Instrument { get; set; }
public int ExpertHandle { get; set; }
}
}
+1 -3
View File
@@ -7,7 +7,6 @@ namespace MtApi5
{
public MqlRates(DateTime time, double open, double high, double low, double close, long tick_volume, int spread, long real_volume)
{
this.time = time;
mt_time = Mt5TimeConverter.ConvertToMtTime(time);
this.open = open;
this.high = high;
@@ -20,7 +19,6 @@ namespace MtApi5
internal MqlRates(long time, double open, double high, double low, double close, long tick_volume, int spread, long real_volume)
{
this.time = Mt5TimeConverter.ConvertFromMtTime(time);
mt_time = time;
this.open = open;
this.high = high;
@@ -35,7 +33,7 @@ namespace MtApi5
{
}
public DateTime time { get; set; } // Period start time
public DateTime time => Mt5TimeConverter.ConvertFromMtTime(mt_time); // Period start time
public long mt_time { get; set; } // Period start time (original MT time)
public double open { get; set; } // Open price
public double high { get; set; } // The highest price of the period
+3 -1
View File
@@ -248,6 +248,8 @@ namespace MtApi5
GlobalVariableTemp = 154,
GlobalVariableSetOnCondition = 156,
GlobalVariablesDeleteAll = 157,
GlobalVariablesTotal = 158
GlobalVariablesTotal = 158,
UnlockTicks = 159
}
}
+18
View File
@@ -0,0 +1,18 @@
using System;
namespace MtApi5
{
public class Mt5TimeBarArgs: EventArgs
{
internal Mt5TimeBarArgs(int expertHandle, string symbol, MqlRates rates)
{
ExpertHandle = expertHandle;
Rates = rates;
Symbol = symbol;
}
public int ExpertHandle { get; }
public string Symbol { get; }
public MqlRates Rates { get; }
}
}
+2
View File
@@ -49,8 +49,10 @@
<ItemGroup>
<Compile Include="CopyTicksFlag.cs" />
<Compile Include="Events\OnBookEvent.cs" />
<Compile Include="Events\OnLastTimeBarEvent.cs" />
<Compile Include="Events\OnTickEvent.cs" />
<Compile Include="Events\OnTradeTransactionEvent.cs" />
<Compile Include="Mt5TimeBarArgs.cs" />
<Compile Include="MqlBookInfo.cs" />
<Compile Include="MqlParam.cs" />
<Compile Include="MqlRates.cs" />
+20
View File
@@ -53,6 +53,7 @@ namespace MtApi5
_mtEventHandlers[Mt5EventTypes.OnBookEvent] = ReceivedOnBookEvent;
_mtEventHandlers[Mt5EventTypes.OnTick] = ReceivedOnTickEvent;
_mtEventHandlers[Mt5EventTypes.OnTradeTransaction] = ReceivedOnTradeTransaction;
_mtEventHandlers[Mt5EventTypes.OnLastTimeBar] = ReceivedOnLastTimeBar;
}
///<summary>
@@ -3057,6 +3058,18 @@ namespace MtApi5
}
#endregion
#region Backtesting functions
///<summary>
///The function unlock ticks in backtesting mode.
///</summary>
public void UnlockTicks()
{
SendCommand<object>(Mt5CommandType.UnlockTicks, null);
}
#endregion
#endregion // Public Methods
#region Properties
@@ -3104,6 +3117,7 @@ namespace MtApi5
public event EventHandler<Mt5ConnectionEventArgs> ConnectionStateChanged;
public event EventHandler<Mt5TradeTransactionEventArgs> OnTradeTransaction;
public event EventHandler<Mt5BookEventArgs> OnBookEvent;
public event EventHandler<Mt5TimeBarArgs> OnLastTimeBar;
#endregion
#region Private Methods
@@ -3216,6 +3230,12 @@ namespace MtApi5
QuoteUpdate?.Invoke(this, new Mt5QuoteEventArgs(quote));
}
private void ReceivedOnLastTimeBar(int expertHandler, string payload)
{
var e = JsonConvert.DeserializeObject<OnLastTimeBarEvent>(payload);
OnLastTimeBar?.Invoke(this, new Mt5TimeBarArgs(expertHandler, e.Instrument, e.Rates));
}
private void Connect(string host, int port)
{
var client = new MtClient(host, port);
+4 -1
View File
@@ -574,6 +574,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Top" Margin="5">
@@ -581,7 +582,9 @@
<Button Command="{Binding ResetLastErrorCommand}" Content="ResetLastError" Margin="2"/>
</WrapPanel>
<Grid Margin="10" Grid.Row="1">
<Button Grid.Row="1" Content="UnlockTicks" HorizontalAlignment="Left" Margin="5" Command="{Binding UnlockTicksCommand}"/>
<Grid Margin="10" Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
+15
View File
@@ -121,6 +121,8 @@ namespace MtApi5TestClient
public DelegateCommand GlobalVariableSetOnConditionCommand { get; private set; }
public DelegateCommand GlobalVariablesDeleteAllCommand { get; private set; }
public DelegateCommand GlobalVariablesTotalCommand { get; private set; }
public DelegateCommand UnlockTicksCommand { get; private set; }
#endregion
#region Properties
@@ -289,6 +291,7 @@ namespace MtApi5TestClient
_mtApiClient.QuoteUpdate += mMtApiClient_QuoteUpdate;
_mtApiClient.OnTradeTransaction += mMtApiClient_OnTradeTransaction;
_mtApiClient.OnBookEvent += _mtApiClient_OnBookEvent;
_mtApiClient.OnLastTimeBar += _mtApiClient_OnOnLastTimeBar;
ConnectionState = _mtApiClient.ConnectionState;
ConnectionMessage = "Disconnected";
@@ -424,6 +427,8 @@ namespace MtApi5TestClient
GlobalVariableSetOnConditionCommand = new DelegateCommand(ExecuteGlobalVariableSetOnCondition);
GlobalVariablesDeleteAllCommand = new DelegateCommand(ExecuteGlobalVariablesDeleteAll);
GlobalVariablesTotalCommand = new DelegateCommand(ExecuteGlobalVariablesTotal);
UnlockTicksCommand = new DelegateCommand(ExecuteUnlockTicks);
}
private bool CanExecuteConnect(object o)
@@ -1564,6 +1569,11 @@ namespace MtApi5TestClient
}
#endregion
private void ExecuteUnlockTicks(object o)
{
_mtApiClient.UnlockTicks();
}
private static void RunOnUiThread(Action action)
{
Application.Current?.Dispatcher.Invoke(action);
@@ -1640,6 +1650,11 @@ namespace MtApi5TestClient
AddLog($"OnBookEvent: ExpertHandle = {e.ExpertHandle}, Symbol = {e.Symbol}");
}
private void _mtApiClient_OnOnLastTimeBar(object sender, Mt5TimeBarArgs e)
{
AddLog($"OnBookEvent: 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}");
}
private void AddQuote(Mt5Quote quote)
{
if (_quotesMap.ContainsKey(quote.ExpertHandle))
BIN
View File
Binary file not shown.
+108 -8
View File
@@ -41,9 +41,17 @@
bool getBooleanValue(int expertHandle, int paramIndex, bool& res, string& err);
#import
//#define __DEBUG_LOG__
#define __DEBUG_LOG__
enum LockTickType
{
NO_LOCK,
LOCK_EVERY_TICK,
LOCK_EVERY_CANDLE
};
input int Port = 8228;
input LockTickType BacktestingLockTicks = NO_LOCK;
int ExpertHandle;
@@ -53,6 +61,9 @@ bool isCrashed = false;
bool IsRemoteReadyForTesting = false;
long _last_bar_open_time = 0;
bool _is_ticks_locked = false;
string PARAM_SEPARATOR = ";";
int OnInit()
@@ -74,14 +85,44 @@ void OnDeinit(const int reason)
void OnTick()
{
string symbol = Symbol();
bool lastbar_time_changed = false;
long lastbar_time = SeriesInfoInteger(symbol, Period(), SERIES_LASTBAR_DATE);
if (_last_bar_open_time != lastbar_time)
{
if (_last_bar_open_time != 0)
{
MqlRates rates_array[];
CopyRates(symbol, Period(), 1, 1, rates_array);
MtTimeBar* timeBar = new MtTimeBar(symbol, rates_array[0]);
SendMtEvent(LAST_TIME_BAR_EVENT, timeBar);
delete timeBar;
lastbar_time_changed = true;
}
_last_bar_open_time = lastbar_time;
}
MqlTick last_tick;
SymbolInfoTick(Symbol(),last_tick);
MtOnTickEvent * tick_event = new MtOnTickEvent(Symbol(), last_tick);
MtOnTickEvent * tick_event = new MtOnTickEvent(symbol, last_tick);
SendMtEvent(ON_TICK_EVENT, tick_event);
delete tick_event;
delete tick_event;
if (IsTesting()) OnTimer();
if (IsTesting())
{
if (BacktestingLockTicks == LOCK_EVERY_TICK ||
(BacktestingLockTicks == LOCK_EVERY_CANDLE && lastbar_time_changed))
{
_is_ticks_locked = true;
}
OnTimer();
}
}
void OnTradeTransaction(
@@ -224,10 +265,12 @@ void OnTimer()
while(true)
{
int executedCommand = executeCommand();
if (_is_ticks_locked)
continue;
if (executedCommand == 0)
{
return;
}
break;
}
}
@@ -741,6 +784,9 @@ int executeCommand()
case 158: //GlobalVariablesTotal
Execute_GlobalVariablesTotal();
break;
case 159: //UnlockTiks
Execute_UnlockTicks();
break;
case 204: //TerminalInfoInteger
Execute_TerminalInfoInteger();
break;
@@ -6461,6 +6507,22 @@ void Execute_GlobalVariablesTotal()
SEND_INT_RESPONSE(res)
}
void Execute_UnlockTicks()
{
if (!IsTesting())
{
Print("WARNING: function UnlockTicks can be used only for backtesting");
return;
}
_is_ticks_locked = false;
if (!sendVoidResponse(ExpertHandle, _response_error))
{
PrintResponseError("UnlockTicks", _response_error);
}
}
void Execute_TerminalInfoInteger()
{
int propertyId;
@@ -7220,7 +7282,8 @@ enum MtEventTypes
{
ON_TRADE_TRANSACTION_EVENT = 1,
ON_BOOK_EVENT = 2,
ON_TICK_EVENT = 3
ON_TICK_EVENT = 3,
LAST_TIME_BAR_EVENT = 4
};
class MtEvent
@@ -7296,6 +7359,29 @@ private:
MqlTick _tick;
};
class MtTimeBar: public MtEvent
{
public:
MtTimeBar(string symbol, const MqlRates& rates)
{
_symbol = symbol;
_rates = rates;
}
virtual JSONObject* CreateJson()
{
JSONObject *jo = new JSONObject();
jo.put("Rates", MqlRatesToJson(_rates));
jo.put("Instrument", new JSONString(_symbol));
jo.put("ExpertHandle", new JSONNumber(ExpertHandle));
return jo;
}
private:
string _symbol;
MqlRates _rates;
};
void SendMtEvent(MtEventTypes eventType, MtEvent* mtEvent)
{
#ifdef __DEBUG_LOG__
@@ -7478,4 +7564,18 @@ JSONObject* MqlTradeRequestToJson(MqlTradeRequest& request)
jo.put("MtExpiration", new JSONNumber((int)request.expiration));
jo.put("Comment", new JSONString(request.comment));
return jo;
}
JSONObject* MqlRatesToJson(MqlRates& rates)
{
JSONObject *jo = new JSONObject();
jo.put("mt_time", new JSONNumber((int)rates.time));
jo.put("open", new JSONNumber(rates.open));
jo.put("high", new JSONNumber(rates.high));
jo.put("low", new JSONNumber(rates.low));
jo.put("close", new JSONNumber(rates.close));
jo.put("tick_volume", new JSONNumber(rates.tick_volume));
jo.put("spread", new JSONNumber(rates.spread));
jo.put("real_volume", new JSONNumber(rates.real_volume));
return jo;
}