Calculate price slippage more reliably and allow specifying order type and time in force
This commit is contained in:
@@ -85,6 +85,27 @@ def to_currency(direction, account, amount, from_currency, to_currency):
|
||||
return converted
|
||||
|
||||
|
||||
def get_price(account, direction, symbol):
|
||||
"""
|
||||
Get the price for a given symbol.
|
||||
:param account: Account object
|
||||
:param direction: direction of the trade
|
||||
:param symbol: symbol
|
||||
:return: price of bid for buys, price of ask for sells
|
||||
"""
|
||||
if direction == "buy":
|
||||
price_index = "bids"
|
||||
elif direction == "sell":
|
||||
price_index = "asks"
|
||||
try:
|
||||
prices = account.client.get_currencies([symbol])
|
||||
except GenericAPIError as e:
|
||||
log.error(f"Error getting currencies: {e}")
|
||||
return None
|
||||
price = D(prices["prices"][0][price_index][0]["price"])
|
||||
return price
|
||||
|
||||
|
||||
def get_trade_size_in_base(direction, account, strategy, cash_balance, base):
|
||||
"""
|
||||
Get the trade size in the base currency.
|
||||
@@ -159,31 +180,57 @@ def get_tp_sl(direction, strategy, price):
|
||||
return (stop_loss, take_profit)
|
||||
|
||||
|
||||
def get_price_bound(direction, strategy, price):
|
||||
def get_price_bound(direction, strategy, price, current_price):
|
||||
"""
|
||||
Get the price bound for a given price using the slippage from the strategy.
|
||||
* Check that the price of the callback is within the callback price deviation of the
|
||||
current price
|
||||
* Calculate the price bounds such that the maximum slippage should be within the
|
||||
price slippage relative to the current price.
|
||||
Note that the maximum actual slippage may be as high as the sum of these two values.
|
||||
:param direction: Direction of the trade
|
||||
:param strategy: Strategy object
|
||||
:param price: Price of the trade
|
||||
:param current_price: current price from the exchange
|
||||
:return: Price bound
|
||||
"""
|
||||
|
||||
# Convert the slippage to a ratio
|
||||
# Convert the callback price deviation to a ratio
|
||||
callback_price_deviation_as_ratio = D(
|
||||
strategy.callback_price_deviation_percent
|
||||
) / D(100)
|
||||
log.debug(f"Callback price deviation as ratio: {callback_price_deviation_as_ratio}")
|
||||
|
||||
maximum_price_deviation = D(current_price) * D(callback_price_deviation_as_ratio)
|
||||
|
||||
# Ensure the current price is within price_slippage_as_ratio of the callback price
|
||||
if abs(current_price - price) <= maximum_price_deviation:
|
||||
log.debug("Current price is within price deviation of callback price")
|
||||
else:
|
||||
log.error("Current price is not within price deviation of callback price")
|
||||
log.debug(f"Difference: {abs(current_price - price)}")
|
||||
return None
|
||||
|
||||
# Convert the maximum price slippage to a ratio
|
||||
price_slippage_as_ratio = D(strategy.price_slippage_percent) / D(100)
|
||||
log.debug(f"Price slippage as ratio: {price_slippage_as_ratio}")
|
||||
log.debug(f"Maximum price slippage as ratio: {price_slippage_as_ratio}")
|
||||
|
||||
# Calculate the price bound by multiplying with the price
|
||||
# The price bound is the worst price we are willing to pay for the trade
|
||||
price_slippage = D(price) * D(price_slippage_as_ratio)
|
||||
log.debug(f"Price slippage: {price_slippage}")
|
||||
price_slippage = D(current_price) * D(price_slippage_as_ratio)
|
||||
log.debug(f"Maximum deviation from callback price: {price_slippage}")
|
||||
|
||||
current_price_slippage = D(current_price) * D(price_slippage_as_ratio)
|
||||
log.debug(f"Maximum deviation from current price: {current_price_slippage}")
|
||||
|
||||
# Subtract slippage for buys, since we lose money if the price goes down
|
||||
if direction == "buy":
|
||||
price_bound = D(price) - D(price_slippage)
|
||||
price_bound = D(current_price) - D(price_slippage)
|
||||
|
||||
# Add slippage for sells, since we lose money if the price goes up
|
||||
elif direction == "sell":
|
||||
price_bound = D(price) + D(price_slippage)
|
||||
price_bound = D(current_price) + D(price_slippage)
|
||||
|
||||
log.debug(f"Price bound: {price_bound}")
|
||||
return price_bound
|
||||
|
||||
@@ -247,22 +294,11 @@ def execute_strategy(callback, strategy):
|
||||
price = round(D(callback.price), display_precision)
|
||||
log.debug(f"Extracted price of quote: {price}")
|
||||
|
||||
# market_from_alpaca = get_market_value(account, symbol)
|
||||
# change_percent = abs(((float(market_from_alpaca)-price)/price)*100)
|
||||
# if change_percent > strategy.price_slippage_percent:
|
||||
# log.error(f"Price slippage too high: {change_percent}")
|
||||
# return False
|
||||
type = strategy.order_type
|
||||
|
||||
# type = "limit"
|
||||
|
||||
# Only using market orders for now, but with price bounds, so it's a similar
|
||||
# amount of protection from market fluctuations
|
||||
# type = "market"
|
||||
|
||||
# For OANDA we can use the price since it should match exactly
|
||||
# Not yet sure how to use both limit and market orders
|
||||
# type = "limit"
|
||||
type = "market"
|
||||
current_price = get_price(account, direction, symbol)
|
||||
log.debug(f"Callback price: {price}")
|
||||
log.debug(f"Current price: {current_price}")
|
||||
|
||||
# Convert the trade size, which is currently in the account's base currency,
|
||||
# to the base currency of the pair we are trading
|
||||
@@ -274,15 +310,18 @@ def execute_strategy(callback, strategy):
|
||||
stop_loss, take_profit = get_tp_sl(direction, strategy, price)
|
||||
|
||||
# Calculate price bound and round to the display precision
|
||||
price_bound = round(get_price_bound(direction, strategy, price), display_precision)
|
||||
price_bound = get_price_bound(direction, strategy, price, current_price)
|
||||
if not price_bound:
|
||||
return
|
||||
price_bound = round(price_bound, display_precision)
|
||||
|
||||
# Use the price reported by the callback for limit orders
|
||||
if type == "limit":
|
||||
price_for_trade = price
|
||||
# # Use the price reported by the callback for limit orders
|
||||
# if type == "limit":
|
||||
# price_for_trade = price
|
||||
|
||||
# Use the price bound for market orders
|
||||
elif type == "market":
|
||||
price_for_trade = price_bound
|
||||
# # Use the price bound for market orders
|
||||
# elif type == "market":
|
||||
# price_for_trade = price_bound
|
||||
|
||||
# Create object, note that the amount is rounded to the trade precision
|
||||
new_trade = Trade.objects.create(
|
||||
@@ -291,10 +330,11 @@ def execute_strategy(callback, strategy):
|
||||
hook=hook,
|
||||
symbol=symbol,
|
||||
type=type,
|
||||
time_in_force=strategy.time_in_force,
|
||||
# amount_fiat=amount_fiat,
|
||||
amount=float(round(trade_size_in_base, trade_precision)),
|
||||
# price=price_bound,
|
||||
price=price_for_trade,
|
||||
price=price_bound,
|
||||
stop_loss=float(round(stop_loss, display_precision)),
|
||||
take_profit=float(round(take_profit, display_precision)),
|
||||
direction=direction,
|
||||
|
||||
Reference in New Issue
Block a user