import math
import MetaTrader5 as Mt5
from abstractEasyT import trade
from supportLibEasyT import log_manager
[docs]class Trade(trade.Trade):
"""
This class is responsible to handle all the trade requests.
"""
[docs] def __init__(self,
symbol: str,
lot: float,
stop_loss: float,
take_profit: float
):
"""
It is allowed to have only one position at time per symbol, right now it is not possible to open a position and
increase the size of it or to open opposite position. Open an open position will close the other direction one.
Args:
symbol:
It is the symbol you want to open or close or check if already have an operation opened.
lot:
It is how many shares you want to trade, many symbols allow fractions and others requires a
certain amount. It can be 0.01, 100.0, 1000.0, 10000.0.
stop_loss:
It is how much you accept to lose. Example: If you buy a share for US$10.00, and you accept to lose US$1.00
you set this variable at 1.00, you will be out of the operation at US$9.00 (sometimes more, somtime less,
the US$9.00 is the trigger). Keep in mind that some symbols has different points metrics, US$1.00 sometimes
can be 1000 points.
take_profit:
It is how much you accept to win. Example: If you buy a share for US$10.00, and you accept to win US$1.00
you set this variable at 1.00, you will be out of the operation at US$11.00 (sometimes more, somtime less,
the US$11.00 is the trigger). Keep in mind that some symbols has different points metrics, US$1.00 sometimes
can be 1000 points.
"""
self._log = log_manager.LogManager('metatrader5')
self._log.logger.info('Logger Initialized in Trade')
self.symbol = symbol.upper()
self.lot = lot
self.stop_loss = stop_loss
self.take_profit = take_profit
self.ticket = None
self.points = Mt5.symbol_info(self.symbol).trade_tick_size
self._trade_allowed = False
self.trade_direction = None # 'buy', 'sell', or None for no position
self.position_check()
[docs] def normalize(self, price: float) -> float:
"""
This function normalize the price to ensure a precision that is required by the platform
Args:
price:
It is the price that you want to be normalized, usually is the last price to open a market position.
Returns:
It returns the float price normalized under a precision that is accepted by the platform.
Examples:
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> # The normalize function is used inside other functions, but the idea is to normalize the value to
>>> # be accepted in the trade request. If you want to see this function in action you can look at
>>> # open_buy() and open_sell()
>>> eurusd_trade.normalize(12.3456789)
12.34
"""
self._log.logger.info('Normalizing the price')
return math.floor(float(self.points * round(price / self.points)) * 100) / 100
[docs] def open_buy(self) -> None:
"""
This functions when called send a buy request to Metatrader5 with the parameters in the attributes.
Returns:
It returns None, but if an error occurs when open a position it will break.
Examples:
Try this on your demo account with fake money, a position will be opened.
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
True
2022-03-11 04:47:29,903 WARNING - initialize_platform - "Algotrading" not allowed! You cannot open position,
but you can retrieve data. If you want to open positions, please, go to Metatrader5 and press the button
"Algotrading."
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> # When I try to open a position and the Algotrading is not allowed on Metatrader5 I got a warning.
>>> eurusd_trade.open_buy()
2022-03-11 04:48:37,068 ERROR - open_buy - Something went wrong: Position Not Found for symbol EURUSD!
Last Error: (1, 'Success')
>>> # But when I allow algotrading, I return None, you can see at Metatrader5 that a buy
position was opened.
>>> eurusd_trade.open_buy()
None
>>> # Just for curiosity, if you want to try to open a sell position with this buy opened you will close
>>> # the buy position
>>> eurusd_trade.open_sell()
None
"""
price = Mt5.symbol_info_tick(self.symbol).ask
self.ticket = (Mt5.positions_get(symbol=self.symbol)[0].ticket if len(
Mt5.positions_get(symbol=self.symbol)) == 1 else 0)
self._log.logger.info(f'BUY Order sent: {self.symbol},'
f' {self.lot} lot(s),'
f' at {price},'
f' stoploss:{self.normalize(price - self.stop_loss)},'
f' takeprofit: {self.normalize(price + self.take_profit)}.')
request = {
"action": Mt5.TRADE_ACTION_DEAL,
"symbol": self.symbol,
"volume": self.lot,
"type": Mt5.ORDER_TYPE_BUY,
"price": price,
"sl": self.normalize(price - self.stop_loss),
"tp": self.normalize(price + self.take_profit),
"deviation": 5,
"magic": 7777,
"comment": "easyT!",
"type_time": Mt5.ORDER_TIME_GTC,
"type_filling": Mt5.ORDER_FILLING_RETURN,
"position": (Mt5.positions_get(symbol=self.symbol)[0].ticket if len(
Mt5.positions_get(symbol=self.symbol)) == 1 else 0)
}
result = Mt5.order_send(request)
if result is None or result.retcode != Mt5.TRADE_RETCODE_DONE:
self._log.logger.error(f'Something went wrong: Position Not Found for symbol {self.symbol}!'
f' Last Error: {Mt5.last_error()}')
else:
self._log.logger.info('Change trade direction to BUY.')
self.trade_direction = 'buy'
[docs] def open_sell(self):
"""
This functions when called send a sell request to Metatrader5 with the parameters in the attributes.
Returns:
It returns None, but if an error occurs when open a position it will break.
Examples:
Try this on your demo account with fake money, a position will be opened.
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
True
2022-03-11 04:47:29,903 WARNING - initialize_platform - "Algotrading" not allowed! You cannot open position,
but you can retrieve data. If you want to open positions, please, go to Metatrader5 and press the button
"Algotrading."
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> # When I try to open a position and the Algotrading is not allowed on Metatrader5 I got a warning.
>>> eurusd_trade.open_sell()
2022-03-11 04:57:08,628 ERROR - open_sell - Something went wrong: Position Not Found for symbol EURUSD!
Last Error: (1, 'Success')
>>> # But when I allow algotrading, I return None, you can see at Metatrader5 that a buy
position was opened.
>>> eurusd_trade.open_sell()
None
>>> # Just for curiosity, if you want to try to open a buy position with this sell opened you will close
>>> # the sell position
>>> eurusd_trade.open_buy()
None
"""
price = Mt5.symbol_info_tick(self.symbol).bid
self.ticket = (Mt5.positions_get(symbol=self.symbol)[0].ticket if len(
Mt5.positions_get(symbol=self.symbol)) == 1 else 0)
self._log.logger.info(f'SELL Order sent: {self.symbol},'
f' {self.lot} lot(s),'
f' at {price},'
f' stoploss:{self.normalize(price - self.stop_loss)},'
f' takeprofit: {self.normalize(price + self.take_profit)}.')
request = {
"action": Mt5.TRADE_ACTION_DEAL,
"symbol": self.symbol,
"volume": self.lot,
"type": Mt5.ORDER_TYPE_SELL,
"price": price,
"sl": self.normalize(price + self.stop_loss),
"tp": self.normalize(price - self.take_profit),
"deviation": 5,
"magic": 7777,
"comment": "easyT",
"type_time": Mt5.ORDER_TIME_GTC,
"type_filling": Mt5.ORDER_FILLING_RETURN,
"position": (Mt5.positions_get(symbol=self.symbol)[0].ticket if len(
Mt5.positions_get(symbol=self.symbol)) == 1 else 0)
}
result = Mt5.order_send(request)
if result is None or result.retcode != Mt5.TRADE_RETCODE_DONE:
self._log.logger.error(f'Something went wrong: Position Not Found for symbol {self.symbol}!'
f' Last Error: {Mt5.last_error()}')
else:
self._log.logger.info('Change trade direction to SELL.')
self.trade_direction = 'sell'
[docs] def position_open(self, buy: bool, sell: bool) -> str or None:
"""
This function receives two bool variables, buy and sell, if one of this variable is true and the other is false,
it opens a position to the side that is true, if both variable is true or both variable is false, it does not
open a position.
Args:
buy:
When buy is TRUE it receives a positive signal to open a position. When false, it is ignored.
sell:
When sell is TRUE it receives a positive signal to open a position. When false, it is ignored.
Returns:
It opens the position.
Examples:
Try this on your demo account with fake money, a position will be opened.
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
True
2022-03-11 04:47:29,903 WARNING - initialize_platform - "Algotrading" not allowed! You cannot open position,
but you can retrieve data. If you want to open positions, please, go to Metatrader5 and press the button
"Algotrading."
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> # When I try to open a position and the Algotrading is not allowed on Metatrader5, return None'
>>> eurusd_trade.position_open(True, False)
None
>>> # I allowed Algotrading, but when I try again, I receive None again.
>>> eurusd_trade.position_open(True, False)
None
>>> # That behavior is happening because the attribute eurusd_trade._trade_allowed, is False as default.
>>> eurusd_trade._trade_allowed
False
>>> # This attribute will be handled in another project, that is why it exists.
>>> # Let assign to True and see what happens:
>>> eurusd_trade._trade_allowed = True
>>> eurusd_trade._trade_allowed
True
>>> # Let try to open a position.
>>> eurusd_trade.position_open(True, False)
'buy'
>>> # It worked, lets try to double the position calling it again
>>> eurusd_trade.position_open(True, False)
'buy'
>>> # The function returns 'buy', but another position was not opened, it returns the current trade
>>> # direction
>>> # I will call an opposite direction to see what happens:
>>> eurusd_trade.position_open(False, True)
'buy'
>>> # The function returns 'buy', the position was not closed by trying to open another one in the opposite
>>> # direction. No new position was opened, and it returns the current trade direction.
>>> # I will close this position with eurusd_trade.position_close(), you can check what it does in the
>>> # function documentation position_close()
>>> eurusd_trade.position_close()
None
>>> # To finish, let see what happens if both arguments are True
>>> eurusd_trade.position_open(True, True)
None
>>> # Nothing happens, but when both are False?
>>> eurusd_trade.position_open(False, False)
None
>>> # Nothing happens
"""
self._log.logger.info(f'Open position called. Buy is {str(buy)}, and sell is {str(sell)}. Trade allowed is '
f'{self._trade_allowed}.')
self.position_check()
if self._trade_allowed and self.trade_direction is None:
if buy and not sell:
self._log.logger.info('BUY is true, SELL is false')
self.open_buy()
self.position_check()
if sell and not buy:
self._log.logger.info('BUY is false, SELL is true')
self.open_sell()
self.position_check()
return self.trade_direction
[docs] def position_close(self) -> None:
"""
This functions checks the trade direction, and it opens an opposite position to the current one to close it.
If there is no position nothing happens.
Returns:
Close the current position by opening an opposite one.
Examples:
Try this on your demo account with fake money, a position will be opened.
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
True
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> eurusd_trade._trade_allowed = True
>>> # Opening a position with Algotrading allowed and, eurusd_trade._trade_allowed True
>>> # To know more about eurusd_trade._trade_allowed look the Examples in position_open() documentation.
>>> eurusd_trade.position_open(False, True)
'sell'
>>> # When there is a position opened, eurusd_trade.position_close() will open a position in a different
>>> # direction to close it.
>>> eurusd_trade.position_close()
None
# It checks the trading direction, return none when there is no trade opened.
>>> eurusd_trade.trade_direction
None
# I will open a buy position, check the trade direction and close it!
>>> eurusd_trade.position_open(True, False)
'buy'
>>> eurusd_trade.trade_direction
'buy'
>>> eurusd_trade.position_close()
>>> # We can see that it worked!
>>> # What happens when I call eurusd_trade.position_close() with no position opened?
>>> eurusd_trade.position_close()
None
>>> # Nothing happens, there are no position to be closed.
"""
self._log.logger.info('Close position called.')
self.position_check()
if self.trade_direction == 'buy':
self._log.logger.info('Close BUY position.')
self.open_sell()
self.position_check()
elif self.trade_direction == 'sell':
self._log.logger.info('Close SELL position')
self.open_buy()
self.position_check()
[docs] def position_check(self) -> None:
"""
This function checks if there are a position opened and update the variable self.trade_direction.
If there is no position, the self.trade_direction will be updated to None, else, it updates with the trade
direction, which can be 'sell' or 'buy'.
Returns:
This function update the variable self.trade_direction and do not return a result.
Examples:
Try this on your demo account with fake money, a position will be opened.
>>> # All the code you need to execute the function:
>>> from metatrader5EasyT.initialization import Initialize
>>> from metatrader5EasyT.trade import Trade
>>> initialize = Initialize()
>>> initialize.initialize_platform()
True
>>> initialize.initialize_symbol('EURUSD')
>>> eurusd_trade = Trade(symbol='EURUSD', lot=1.0, stop_loss=1.0, take_profit=1.0)
>>> eurusd_trade._trade_allowed = True
>>> # Position check it is just to ensure that the eurusd_trade.trade_direction are in the right direction.
>>> # The eurusd_trade.trade_direction is automatically handled by buy open_sell() and open_buy() and
>>> # it returns the trade direction or None when there is no trade opened.
>>> eurusd_trade.trade_direction
None
>>> eurusd_trade.position_open(True, False)
'buy'
>>> eurusd_trade.trade_direction
'buy'
>>> # After I open a buy position, it returns 'buy' to trade_direction, but, what happens if I manually
>>> # change the direction?
>>> eurusd_trade.trade_direction = 'coffee shop'
None
>>> eurusd_trade.trade_direction
'coffee shop'
>>> # It is possible to see that the trade_direction was changed.
>>> # and the position_check() is called in all the functions that opens and closes position
>>> # to ensure that direction is correct, I will call position_check() to fix my change to 'coffee shop'
>>> eurusd_trade.position_check()
None
>>> eurusd_trade.trade_direction
'buy'
>>> # It worked.
>>> # That is it, I will just the position that I opened before.
>>> eurusd_trade.position_close()
"""
self._log.logger.info('Calls Metatrader5 to check if there is a position opened.')
result = Mt5.positions_get(symbol=self.symbol)
if len(result) > 0:
self._log.logger.info('There is a position opened.')
if result[0].type == 0: # if buy
self._log.logger.info('Set the trade direction to BUY')
self.trade_direction = 'buy'
elif result[0].type == 1: # if sell
self._log.logger.info('Set the trade direction to SELL')
self.trade_direction = 'sell'
else:
self._log.logger.info('There are no position opened.')
self._log.logger.info('Set the trade direction to None')
self.trade_direction = None