diff --git a/PyMtApi5/client.py b/PyMtApi5/client.py index afc59be3..5de819c3 100644 --- a/PyMtApi5/client.py +++ b/PyMtApi5/client.py @@ -14,6 +14,7 @@ logger = logging.getLogger(__name__) def signal_handler(mtapi, _, __): + del __ if mtapi.is_connected(): mtapi.disconnect() @@ -48,6 +49,14 @@ class Mt5ApiApp: "CopyBuffer": self.process_copy_buffer, "CopyRates": self.process_copy_rates, "CopyTime": self.process_copy_time, + "CopyOpen": self.process_copy_open, + "CopyHigh": self.process_copy_high, + "CopyLow": self.process_copy_low, + "CopyClose": self.process_copy_close, + "CopyTickVolume": self.process_copy_tick_volume, + "CopyRealVolume": self.process_copy_real_volume, + "CopySpread": self.process_copy_spread, + "CopyTicks": self.process_copy_ticks, } def on_disconnect(self, error_msg=None): @@ -317,6 +326,102 @@ class Mt5ApiApp: result = mtapi.copy_time(symbol_name, timeframe, start_pos, count) print(f"> CopyTime: response = {result}") + def process_copy_open(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyOpen: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_open(symbol_name, timeframe, start_pos, count) + print(f"> CopyOpen: response = {result}") + + def process_copy_high(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyHigh: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_high(symbol_name, timeframe, start_pos, count) + print(f"> CopyHigh: response = {result}") + + def process_copy_low(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyLow: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_low(symbol_name, timeframe, start_pos, count) + print(f"> CopyLow: response = {result}") + + def process_copy_close(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyClose: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_close(symbol_name, timeframe, start_pos, count) + print(f"> CopyClose: response = {result}") + + def process_copy_tick_volume(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyTickVolume: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_tick_volume(symbol_name, timeframe, start_pos, count) + print(f"> CopyTickVolume: response = {result}") + + def process_copy_real_volume(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyRealVolume: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_real_volume(symbol_name, timeframe, start_pos, count) + print(f"> CopyRealVolume: response = {result}") + + def process_copy_spread(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopySpread: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.ENUM_TIMEFRAMES(int(pieces[1])) + start_pos = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_spread(symbol_name, timeframe, start_pos, count) + print(f"> CopySpread: response = {result}") + + def process_copy_ticks(self, mtapi, parameters): + pieces = parameters.split(" ", 3) + if len(pieces) != 4 or not pieces[0] or not pieces[1] or not pieces[2] or not pieces[3]: + print(f"! Invalid parameters for command CopyTicks: {parameters}") + return + symbol_name = pieces[0] + timeframe = mt5enums.CopyTicksFlag(int(pieces[1])) + from_date = int(pieces[2]) + count = int(pieces[3]) + result = mtapi.copy_ticks(symbol_name, timeframe, from_date, count) + print(f"> CopyTicks: response = {result}") + def mtapi_command_thread(self, mtapi): while mtapi.is_connected(): filename = "client.cmd" diff --git a/PyMtApi5/mt5apiclient.py b/PyMtApi5/mt5apiclient.py index e75dd453..534dd3fb 100644 --- a/PyMtApi5/mt5apiclient.py +++ b/PyMtApi5/mt5apiclient.py @@ -29,15 +29,16 @@ class Mt5Quote: return f"{self.expert_handle}-{self.instrument}: Bid = {self.bid}, Ask = {self.ask}, Volume = {self.volume}" -class Mql5Tick: +class MqlTick: def __init__(self, tick_json): self.bid = tick_json["Bid"] self.ask = tick_json["Ask"] self.last = tick_json["Last"] self.volume = tick_json["Volume"] + self.time = tick_json["Time"] def __repr__(self): - return f"Bid = {self.bid}, Ask = {self.ask}, Last = {self.last}, Volume = {self.volume}" + return f"Bid = {self.bid}, Ask = {self.ask}, Last = {self.last}, Volume = {self.volume}, Time = {self.time}" class MqlRates: @@ -142,23 +143,6 @@ class MqlBookInfo: return f"book_type = {self.book_type}, price = {self.price}, volume = {self.volume}, volume_real = {self.volume_real}" -class MqlRates: - def __init__(self, mql_rates_json): - self.open = mql_rates_json["open"] - self.high = mql_rates_json["high"] - self.low = mql_rates_json["low"] - self.close = mql_rates_json["close"] - self.tick_volume = mql_rates_json["tick_volume"] - self.spread = mql_rates_json["spread"] - self.real_volume = mql_rates_json["real_volume"] - - def __repr__(self): - return ( - f"open = {self.open}, high = {self.high}, low = {self.low}, close = {self.close}, tick_volume = {self.tick_volume}, " - f"spread = {self.spread}, real_volume = {self.real_volume}" - ) - - class Mt5ApiClient: def __init__(self, address, port, callback=None): self.__address = address @@ -264,75 +248,117 @@ class Mt5ApiClient: # CopyBuffer def copy_buffer(self, indicator_handle: int, buffer_num: int, start_pos: int, count: int): - cmdParams = { + cmd_params = { "IndicatorHandle": indicator_handle, "BufferNum": buffer_num, "StartPos": start_pos, "Count": count, } - return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyBuffer, cmdParams) + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyBuffer, cmd_params) # CopyRates def copy_rates(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): - cmdParams = { + cmd_params = { "Symbol": symbol_name, "Timeframe": timeframe, "StartPos": start_pos, "Count": count, } - res = self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyRates, cmdParams) + res = self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyRates, cmd_params) rates = [MqlRates(obj) for obj in res] return rates # CopyTime def copy_time(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): - cmdParams = { + cmd_params = { "Symbol": symbol_name, "Timeframe": timeframe, "StartPos": start_pos, "Count": count, } - return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyTime, cmdParams) + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyTime, cmd_params) - # Copy Open - def copy_open(self): - # TODO - pass + # CopyOpen + def copy_open(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyOpen, cmd_params) - # Copy High - def copy_high(self): - # TODO - pass + # CopyHigh + def copy_high(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyHigh, cmd_params) # CopyLow - def copy_low(self): - # TODO - pass + def copy_low(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyLow, cmd_params) # CopyClose - def copy_close(self): - # TODO - pass + def copy_close(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyClose, cmd_params) # CopyTickVolume - def copy_tick_volume(self): - # TODO - pass + def copy_tick_volume(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyTickVolume, cmd_params) # CopyRealVolume - def copy_real_volume(self): - # TODO - pass + def copy_real_volume(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyRealVolume, cmd_params) # CopySpread - def copy_spread(self): - # TODO - pass + def copy_spread(self, symbol_name: str, timeframe: ENUM_TIMEFRAMES, start_pos: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Timeframe": timeframe, + "StartPos": start_pos, + "Count": count, + } + return self.__send_command(self.__get_default_expert(), Mt5CommandType.CopySpread, cmd_params) # CopyTicks - def copy_ticks(self): - # TODO - pass + def copy_ticks(self, symbol_name: str, flags: CopyTicksFlag, from_date: int, count: int): + cmd_params = { + "Symbol": symbol_name, + "Flags": flags, + "From": from_date, + "Count": count, + } + res = self.__send_command(self.__get_default_expert(), Mt5CommandType.CopyTicks, cmd_params) + ticks = [MqlTick(obj) for obj in res] + return ticks # IndicatorCreate def indicator_create( @@ -392,7 +418,7 @@ class Mt5ApiClient: cmd_params = {"Symbol": symbol_name} res = self.__send_command(self.__get_default_expert(), Mt5CommandType.SymbolInfoTick, cmd_params) if res is not None and res["RetVal"] == True: - return Mql5Tick(res["Result"]) + return MqlTick(res["Result"]) return None # SymbolInfoSessionQuote diff --git a/PyMtApi5/mt5enums.py b/PyMtApi5/mt5enums.py index d47c51ce..1dac013a 100644 --- a/PyMtApi5/mt5enums.py +++ b/PyMtApi5/mt5enums.py @@ -894,3 +894,8 @@ class ENUM_DATATYPE(IntEnum): TYPE_FLOAT = 12 TYPE_DOUBLE = 13 TYPE_STRING = 14 + +class CopyTicksFlag(IntEnum): + Info = 1 # ticks with Bid and/or Ask changes + Trade = 2 # ticks with changes in Last and Volume + All = -1 # all ticks