from core.exchanges import GenericAPIError from core.util import logs log = logs.get_logger(__name__) def check_existing_position( func: str, position: dict, open_side: str, open_symbol: str, open_units: str, new_side: str, new_symbol: str, trade_side_opposite: str, ): # Check if we already have a position for the symbol if open_symbol == new_symbol: # If the live side is the inverse of what we want to do, # we can't open a position if open_side == trade_side_opposite: # If there is a position open, we can't open a new one in the opposite # direction if open_units != "0": # If we have a short on GBP/AUD, we can only place more shorts on # GBP/AUD. if func == "entry": log.debug( f"Refusing to open new {new_side} position on {new_symbol} due " f"to {open_side} position on {open_symbol}" ) return { "action": "rejected", "positions": position, } elif func == "exit": log.debug( ( f"Found {open_units} units of " f"{open_symbol} on side {trade_side_opposite}" ) ) # Pass back opposing side so we can close it return { "action": "close", "side": trade_side_opposite, "positions": position, } return False def check_conflicting_position( func: str, position: dict, open_base: str, open_quote: str, open_side: str, open_symbol: str, open_units: str, new_base: str, new_quote: str, new_side: str, new_symbol: str, trade_side_opposite: str, ): if open_base == new_quote or open_quote == new_base: # If we have a long on GBP/AUD, we can only place shorts on XAU/GBP. if open_side != trade_side_opposite: if open_units != "0": # Only do this for entries if func == "entry": log.debug( f"Refusing to open {new_side} position on {new_symbol} due to " f"{open_side} position on {open_symbol}" ) return { "action": "rejected", "positions": position, } def crossfilter(account, new_symbol, new_direction, func): """ Determine if we are betting against ourselves. Checks open positions for the account, rejecting the trade if there is one with an opposite direction to this one. :param account: Account object :param symbol: Symbol :param direction: Direction of the trade :param func: Whether we are checking entries or exits :return: dict of action and opposing position, or False """ try: # Only get the data we need if func == "entry": all_positions = account.client.get_all_positions() else: all_positions = [account.client.get_position_info(new_symbol)] except GenericAPIError as e: if "No position exists for the specified instrument" in str(e): log.debug("No position exists for this symbol") return False else: log.error(f"Error getting position info: {e}") return None if new_direction == "buy": opposing_side = "short" new_side = "long" elif new_direction == "sell": opposing_side = "long" new_side = "short" quotes = [] new_base, new_quote = new_symbol.split("_") for position in all_positions: # For Forex, get a list of all the quotes. # This is to prevent betting against ourselves. # Consider we have a long position open, EUR/USD, and we want to open a # long position on USD/JPY. If the first goes up, the second one will go # down just as much. We won't make any money. if "_" in position["symbol"]: open_base, open_quote = position["symbol"].split("_") quotes.append(open_quote) open_symbol = position["symbol"] open_side = position["side"] open_base, open_quote = open_symbol.split("_") # Check if we already have a position existing_position_check = check_existing_position( func=func, position=position, open_side=open_side, open_symbol=open_symbol, open_units=position["units"], new_side=new_side, new_symbol=new_symbol, trade_side_opposite=opposing_side, ) if existing_position_check: return existing_position_check # Check if we are betting against ourselves conflicting_position_check = check_conflicting_position( func=func, position=position, open_base=open_base, open_quote=open_quote, open_side=open_side, open_symbol=open_symbol, open_units=position["units"], new_base=new_base, new_quote=new_quote, new_side=new_side, new_symbol=new_symbol, trade_side_opposite=opposing_side, ) if conflicting_position_check: return conflicting_position_check return False