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 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 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) : public Mt5Expert(int handle, string symbol, double bid, double ask, IMetaTraderHandler mtHandler, bool isTestMode) :
base(handle, symbol, bid, ask, mtHandler) base(handle, symbol, bid, ask, mtHandler)
{ {
IsTestMode = isTestMode; IsTestMode = isTestMode;
_stopTimer.Interval = StopExpertInterval;
_stopTimer.Elapsed += _stopTimer_Elapsed;
} }
public bool IsTestMode { get; } public bool IsTestMode { get; }
public override void UpdateQuote(MtQuote quote) public override int GetCommandType()
{ {
Log.Debug("UpdateQuote: begin."); Log.Debug("GetCommandType: called.");
base.UpdateQuote(quote);
if (IsTestMode) if (IsTestMode)
{ {
//reset timer ResetTestModeTimer();
_stopTimer.Stop();
_stopTimer.Start();
} }
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) 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."); 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(); Deinit();
_stopTimer.Elapsed -= _stopTimer_Elapsed;
_stopTimer = null;
Log.Debug("_stopTimer_Elapsed: end."); 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); server.AddExpert(expert);
expert.Deinited += ExpertOnDeinited;
Log.Info("AddExpert: end"); Log.Info("AddExpert: end");
} }
@@ -81,7 +82,6 @@ namespace MTApiService
if (_experts.ContainsKey(expertHandle)) if (_experts.ContainsKey(expertHandle))
{ {
expert = _experts[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 #endregion
} }
} }
+3 -3
View File
@@ -48,7 +48,7 @@ namespace MTApiService
Log.Debug("SendResponse: end."); Log.Debug("SendResponse: end.");
} }
public int GetCommandType() public virtual int GetCommandType()
{ {
Log.Debug("GetCommandType: called."); Log.Debug("GetCommandType: called.");
@@ -110,7 +110,7 @@ namespace MTApiService
return command.NamedParams.ContainsKey(name); return command.NamedParams.ContainsKey(name);
} }
public void SendEvent(MtEvent mtEvent) public virtual void SendEvent(MtEvent mtEvent)
{ {
Log.DebugFormat("SendEvent: begin. event = {0}", mtEvent); Log.DebugFormat("SendEvent: begin. event = {0}", mtEvent);
@@ -119,7 +119,7 @@ namespace MTApiService
Log.Debug("SendEvent: end."); Log.Debug("SendEvent: end.");
} }
public virtual void UpdateQuote(MtQuote quote) public void UpdateQuote(MtQuote quote)
{ {
Log.DebugFormat("UpdateQuote: begin. quote = {0}", quote); Log.DebugFormat("UpdateQuote: begin. quote = {0}", quote);
+2 -1
View File
@@ -4,6 +4,7 @@
{ {
OnTradeTransaction = 1, OnTradeTransaction = 1,
OnBookEvent = 2, 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) 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); mt_time = Mt5TimeConverter.ConvertToMtTime(time);
this.open = open; this.open = open;
this.high = high; 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) 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; mt_time = time;
this.open = open; this.open = open;
this.high = high; 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 long mt_time { get; set; } // Period start time (original MT time)
public double open { get; set; } // Open price public double open { get; set; } // Open price
public double high { get; set; } // The highest price of the period public double high { get; set; } // The highest price of the period
+3 -1
View File
@@ -248,6 +248,8 @@ namespace MtApi5
GlobalVariableTemp = 154, GlobalVariableTemp = 154,
GlobalVariableSetOnCondition = 156, GlobalVariableSetOnCondition = 156,
GlobalVariablesDeleteAll = 157, 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> <ItemGroup>
<Compile Include="CopyTicksFlag.cs" /> <Compile Include="CopyTicksFlag.cs" />
<Compile Include="Events\OnBookEvent.cs" /> <Compile Include="Events\OnBookEvent.cs" />
<Compile Include="Events\OnLastTimeBarEvent.cs" />
<Compile Include="Events\OnTickEvent.cs" /> <Compile Include="Events\OnTickEvent.cs" />
<Compile Include="Events\OnTradeTransactionEvent.cs" /> <Compile Include="Events\OnTradeTransactionEvent.cs" />
<Compile Include="Mt5TimeBarArgs.cs" />
<Compile Include="MqlBookInfo.cs" /> <Compile Include="MqlBookInfo.cs" />
<Compile Include="MqlParam.cs" /> <Compile Include="MqlParam.cs" />
<Compile Include="MqlRates.cs" /> <Compile Include="MqlRates.cs" />
+20
View File
@@ -53,6 +53,7 @@ namespace MtApi5
_mtEventHandlers[Mt5EventTypes.OnBookEvent] = ReceivedOnBookEvent; _mtEventHandlers[Mt5EventTypes.OnBookEvent] = ReceivedOnBookEvent;
_mtEventHandlers[Mt5EventTypes.OnTick] = ReceivedOnTickEvent; _mtEventHandlers[Mt5EventTypes.OnTick] = ReceivedOnTickEvent;
_mtEventHandlers[Mt5EventTypes.OnTradeTransaction] = ReceivedOnTradeTransaction; _mtEventHandlers[Mt5EventTypes.OnTradeTransaction] = ReceivedOnTradeTransaction;
_mtEventHandlers[Mt5EventTypes.OnLastTimeBar] = ReceivedOnLastTimeBar;
} }
///<summary> ///<summary>
@@ -3057,6 +3058,18 @@ namespace MtApi5
} }
#endregion #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 #endregion // Public Methods
#region Properties #region Properties
@@ -3104,6 +3117,7 @@ namespace MtApi5
public event EventHandler<Mt5ConnectionEventArgs> ConnectionStateChanged; public event EventHandler<Mt5ConnectionEventArgs> ConnectionStateChanged;
public event EventHandler<Mt5TradeTransactionEventArgs> OnTradeTransaction; public event EventHandler<Mt5TradeTransactionEventArgs> OnTradeTransaction;
public event EventHandler<Mt5BookEventArgs> OnBookEvent; public event EventHandler<Mt5BookEventArgs> OnBookEvent;
public event EventHandler<Mt5TimeBarArgs> OnLastTimeBar;
#endregion #endregion
#region Private Methods #region Private Methods
@@ -3216,6 +3230,12 @@ namespace MtApi5
QuoteUpdate?.Invoke(this, new Mt5QuoteEventArgs(quote)); 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) private void Connect(string host, int port)
{ {
var client = new MtClient(host, port); var client = new MtClient(host, port);
+4 -1
View File
@@ -574,6 +574,7 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Top" Margin="5"> <WrapPanel VerticalAlignment="Top" Margin="5">
@@ -581,7 +582,9 @@
<Button Command="{Binding ResetLastErrorCommand}" Content="ResetLastError" Margin="2"/> <Button Command="{Binding ResetLastErrorCommand}" Content="ResetLastError" Margin="2"/>
</WrapPanel> </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> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
+15
View File
@@ -121,6 +121,8 @@ namespace MtApi5TestClient
public DelegateCommand GlobalVariableSetOnConditionCommand { get; private set; } public DelegateCommand GlobalVariableSetOnConditionCommand { get; private set; }
public DelegateCommand GlobalVariablesDeleteAllCommand { get; private set; } public DelegateCommand GlobalVariablesDeleteAllCommand { get; private set; }
public DelegateCommand GlobalVariablesTotalCommand { get; private set; } public DelegateCommand GlobalVariablesTotalCommand { get; private set; }
public DelegateCommand UnlockTicksCommand { get; private set; }
#endregion #endregion
#region Properties #region Properties
@@ -289,6 +291,7 @@ namespace MtApi5TestClient
_mtApiClient.QuoteUpdate += mMtApiClient_QuoteUpdate; _mtApiClient.QuoteUpdate += mMtApiClient_QuoteUpdate;
_mtApiClient.OnTradeTransaction += mMtApiClient_OnTradeTransaction; _mtApiClient.OnTradeTransaction += mMtApiClient_OnTradeTransaction;
_mtApiClient.OnBookEvent += _mtApiClient_OnBookEvent; _mtApiClient.OnBookEvent += _mtApiClient_OnBookEvent;
_mtApiClient.OnLastTimeBar += _mtApiClient_OnOnLastTimeBar;
ConnectionState = _mtApiClient.ConnectionState; ConnectionState = _mtApiClient.ConnectionState;
ConnectionMessage = "Disconnected"; ConnectionMessage = "Disconnected";
@@ -424,6 +427,8 @@ namespace MtApi5TestClient
GlobalVariableSetOnConditionCommand = new DelegateCommand(ExecuteGlobalVariableSetOnCondition); GlobalVariableSetOnConditionCommand = new DelegateCommand(ExecuteGlobalVariableSetOnCondition);
GlobalVariablesDeleteAllCommand = new DelegateCommand(ExecuteGlobalVariablesDeleteAll); GlobalVariablesDeleteAllCommand = new DelegateCommand(ExecuteGlobalVariablesDeleteAll);
GlobalVariablesTotalCommand = new DelegateCommand(ExecuteGlobalVariablesTotal); GlobalVariablesTotalCommand = new DelegateCommand(ExecuteGlobalVariablesTotal);
UnlockTicksCommand = new DelegateCommand(ExecuteUnlockTicks);
} }
private bool CanExecuteConnect(object o) private bool CanExecuteConnect(object o)
@@ -1564,6 +1569,11 @@ namespace MtApi5TestClient
} }
#endregion #endregion
private void ExecuteUnlockTicks(object o)
{
_mtApiClient.UnlockTicks();
}
private static void RunOnUiThread(Action action) private static void RunOnUiThread(Action action)
{ {
Application.Current?.Dispatcher.Invoke(action); Application.Current?.Dispatcher.Invoke(action);
@@ -1640,6 +1650,11 @@ namespace MtApi5TestClient
AddLog($"OnBookEvent: ExpertHandle = {e.ExpertHandle}, Symbol = {e.Symbol}"); 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) private void AddQuote(Mt5Quote quote)
{ {
if (_quotesMap.ContainsKey(quote.ExpertHandle)) if (_quotesMap.ContainsKey(quote.ExpertHandle))
BIN
View File
Binary file not shown.
+107 -7
View File
@@ -41,9 +41,17 @@
bool getBooleanValue(int expertHandle, int paramIndex, bool& res, string& err); bool getBooleanValue(int expertHandle, int paramIndex, bool& res, string& err);
#import #import
//#define __DEBUG_LOG__ #define __DEBUG_LOG__
enum LockTickType
{
NO_LOCK,
LOCK_EVERY_TICK,
LOCK_EVERY_CANDLE
};
input int Port = 8228; input int Port = 8228;
input LockTickType BacktestingLockTicks = NO_LOCK;
int ExpertHandle; int ExpertHandle;
@@ -53,6 +61,9 @@ bool isCrashed = false;
bool IsRemoteReadyForTesting = false; bool IsRemoteReadyForTesting = false;
long _last_bar_open_time = 0;
bool _is_ticks_locked = false;
string PARAM_SEPARATOR = ";"; string PARAM_SEPARATOR = ";";
int OnInit() int OnInit()
@@ -74,14 +85,44 @@ void OnDeinit(const int reason)
void OnTick() 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; MqlTick last_tick;
SymbolInfoTick(Symbol(),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); 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( void OnTradeTransaction(
@@ -224,10 +265,12 @@ void OnTimer()
while(true) while(true)
{ {
int executedCommand = executeCommand(); int executedCommand = executeCommand();
if (_is_ticks_locked)
continue;
if (executedCommand == 0) if (executedCommand == 0)
{ break;
return;
}
} }
} }
@@ -741,6 +784,9 @@ int executeCommand()
case 158: //GlobalVariablesTotal case 158: //GlobalVariablesTotal
Execute_GlobalVariablesTotal(); Execute_GlobalVariablesTotal();
break; break;
case 159: //UnlockTiks
Execute_UnlockTicks();
break;
case 204: //TerminalInfoInteger case 204: //TerminalInfoInteger
Execute_TerminalInfoInteger(); Execute_TerminalInfoInteger();
break; break;
@@ -6461,6 +6507,22 @@ void Execute_GlobalVariablesTotal()
SEND_INT_RESPONSE(res) 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() void Execute_TerminalInfoInteger()
{ {
int propertyId; int propertyId;
@@ -7220,7 +7282,8 @@ enum MtEventTypes
{ {
ON_TRADE_TRANSACTION_EVENT = 1, ON_TRADE_TRANSACTION_EVENT = 1,
ON_BOOK_EVENT = 2, ON_BOOK_EVENT = 2,
ON_TICK_EVENT = 3 ON_TICK_EVENT = 3,
LAST_TIME_BAR_EVENT = 4
}; };
class MtEvent class MtEvent
@@ -7296,6 +7359,29 @@ private:
MqlTick _tick; 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) void SendMtEvent(MtEventTypes eventType, MtEvent* mtEvent)
{ {
#ifdef __DEBUG_LOG__ #ifdef __DEBUG_LOG__
@@ -7479,3 +7565,17 @@ JSONObject* MqlTradeRequestToJson(MqlTradeRequest& request)
jo.put("Comment", new JSONString(request.comment)); jo.put("Comment", new JSONString(request.comment));
return jo; 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;
}