You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
6.4 KiB
Python

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,
):
"""
Determine if we have a conflicting position open.
:param func: Whether we are checking entries, exits or trends
:param position: Position dict
:param open_base: Base currency of the open position
:param open_quote: Quote currency of the open position
:param open_side: Side of the open position
:param open_symbol: Symbol of the open position
:param open_units: Units of the open position
:param new_base: Base currency of the new position
:param new_quote: Quote currency of the new position
:param new_side: Side of the new position
:param new_symbol: Symbol of the new position
:param trade_side_opposite: Opposite side of the trade
:return: dict of action and opposing position, or None
"""
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, all_positions=None):
"""
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 new_symbol: Symbol of the new position
:param new_direction: Direction of the new position
:param func: Whether we are checking entries, exits or trends
:return: dict of action and opposing position, False or None
"""
try:
# Only get the data we need
if func == "entry":
if all_positions is None:
all_positions = account.client.get_all_positions()
else:
if all_positions is None:
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