Update pricing to use entry/exit pricing

This commit is contained in:
Matthias 2022-03-28 07:03:10 +02:00
parent 9f863369bd
commit f70166270d
4 changed files with 56 additions and 32 deletions

View File

@ -193,13 +193,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
ask_strategy = conf.get('ask_strategy', {})
ask_strategy = conf.get('exit_pricing', {})
ob_min = ask_strategy.get('order_book_min')
ob_max = ask_strategy.get('order_book_max')
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
if ob_min != ob_max:
raise OperationalException(
"Using order_book_max != order_book_min in ask_strategy is no longer supported."
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
"Please pick one value and use `order_book_top` in the future."
)
else:
@ -208,7 +208,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
logger.warning(
"DEPRECATED: "
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
"for your `ask_strategy` configuration."
"for your `exit_pricing` configuration."
)
@ -295,10 +295,10 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
"Please migrate your settings to use 'entry_pricing' and 'exit_pricing'."
)
conf['entry_pricing'] = {}
for obj in list(conf.get('bid_strategy').keys()):
for obj in list(conf.get('bid_strategy', {}).keys()):
process_deprecated_setting(conf, 'bid_strategy', obj, 'entry_pricing', obj)
del conf['bid_strategy']
conf['exit_pricing'] = {}
for obj in list(conf.get('ask_strategy').keys()):
for obj in list(conf.get('ask_strategy', {}).keys()):
process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj)
del conf['ask_strategy']

View File

@ -111,8 +111,8 @@ class Exchange:
# Cache values for 1800 to avoid frequent polling of the exchange for prices
# Caching only applies to RPC methods, so prices for open trades are still
# refreshed once every iteration.
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
# Holds candles
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
@ -1438,7 +1438,8 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def get_rate(self, pair: str, refresh: bool, side: str) -> float:
def get_rate(self, pair: str, refresh: bool,
side: Literal['entry', 'exit'], is_short: bool) -> float:
"""
Calculates bid/ask target
bid rate - between current ask price and last price
@ -1450,9 +1451,10 @@ class Exchange:
:return: float: Price
:raises PricingError if orderbook price could not be determined.
"""
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
name = side.capitalize()
strat_name = 'entry_pricing' if side == "entry" else 'exit_pricing'
cache_rate: TTLCache = self._entry_rate_cache if side == "entry" else self._exit_rate_cache
if not refresh:
rate = cache_rate.get(pair)
# Check if cache has been invalidated
@ -1462,27 +1464,43 @@ class Exchange:
conf_strategy = self._config.get(strat_name, {})
if conf_strategy.get('use_order_book', False):
price_side = conf_strategy['price_side']
if price_side in ('same', 'other'):
price_map = {
('enter', 'long', 'same'): 'bid',
('enter', 'long', 'other'): 'ask',
('enter', 'short', 'same'): 'ask',
('enter', 'short', 'other'): 'bid',
('exit', 'long', 'same'): 'ask',
('exit', 'long', 'other'): 'bid',
('exit', 'short', 'same'): 'bid',
('exit', 'short', 'other'): 'ask',
}
price_side = price_map[(side, 'short' if is_short else 'long', price_side)]
price_side_word = price_side.capitalize()
if conf_strategy.get('use_order_book', True):
order_book_top = conf_strategy.get('order_book_top', 1)
order_book = self.fetch_l2_order_book(pair, order_book_top)
logger.debug('order_book %s', order_book)
# top 1 = index 0
try:
rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0]
rate = order_book[f"{price_side}s"][order_book_top - 1][0]
except (IndexError, KeyError) as e:
logger.warning(
f"{name} Price at location {order_book_top} from orderbook could not be "
f"determined. Orderbook: {order_book}"
)
raise PricingError from e
price_side = {conf_strategy['price_side'].capitalize()}
logger.debug(f"{name} price from orderbook {price_side}"
logger.debug(f"{name} price from orderbook {price_side_word}"
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
else:
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
logger.debug(f"Using Last {price_side_word} / Last Price")
ticker = self.fetch_ticker(pair)
ticker_rate = ticker[conf_strategy['price_side']]
ticker_rate = ticker[price_side]
if ticker['last'] and ticker_rate:
if side == 'buy' and ticker_rate > ticker['last']:
balance = conf_strategy.get('ask_last_balance', 0.0)

View File

@ -7,7 +7,7 @@ import traceback
from datetime import datetime, time, timezone
from math import isclose
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Literal, Optional, Tuple
from schedule import Scheduler
@ -511,7 +511,8 @@ class FreqtradeBot(LoggingMixin):
return
else:
logger.debug("Max adjustment entries is set to unlimited.")
current_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.enter_side)
current_rate = self.exchange.get_rate(
trade.pair, side='entry', is_short=trade.is_short, refresh=True)
current_profit = trade.calc_profit_ratio(current_rate)
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
@ -589,11 +590,11 @@ class FreqtradeBot(LoggingMixin):
time_in_force = self.strategy.order_time_in_force['entry']
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
trade_side = 'short' if is_short else 'long'
trade_side: Literal['long', 'short'] = 'short' if is_short else 'long'
pos_adjust = trade is not None
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
pair, price, stake_amount, side, trade_side, enter_tag, trade)
pair, price, stake_amount, trade_side, enter_tag, trade)
if not stake_amount:
return False
@ -745,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
def get_valid_enter_price_and_stake(
self, pair: str, price: Optional[float], stake_amount: float,
side: str, trade_side: str,
trade_side: Literal['long', 'short'],
entry_tag: Optional[str],
trade: Optional[Trade]
) -> Tuple[float, float, float]:
@ -754,7 +755,8 @@ class FreqtradeBot(LoggingMixin):
enter_limit_requested = price
else:
# Calculate price
proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side=side)
proposed_enter_rate = self.exchange.get_rate(
pair, side='entry', is_short=(trade_side == 'short'), refresh=True)
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=proposed_enter_rate)(
pair=pair, current_time=datetime.now(timezone.utc),
@ -763,7 +765,7 @@ class FreqtradeBot(LoggingMixin):
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
if not enter_limit_requested:
raise PricingError(f'Could not determine {side} price.')
raise PricingError('Could not determine entry price.')
if trade is None:
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
@ -824,7 +826,8 @@ class FreqtradeBot(LoggingMixin):
current_rate = trade.open_rate_requested
if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
current_rate = self.exchange.get_rate(
trade.pair, side='entry', is_short=trade.is_short, refresh=False)
msg = {
'trade_id': trade.id,
@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
"""
Sends rpc notification when a entry order cancel occurred.
"""
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
current_rate = self.exchange.get_rate(
trade.pair, side='entry', is_short=trade.is_short, refresh=False)
msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
msg = {
'trade_id': trade.id,
@ -935,7 +939,8 @@ class FreqtradeBot(LoggingMixin):
)
logger.debug('checking exit')
exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side)
exit_rate = self.exchange.get_rate(
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
return True
@ -1433,7 +1438,7 @@ class FreqtradeBot(LoggingMixin):
profit_trade = trade.calc_profit(rate=profit_rate)
# Use cached rates here - it was updated seconds ago.
current_rate = self.exchange.get_rate(
trade.pair, refresh=False, side=trade.exit_side) if not fill else None
trade.pair, side='exit', is_short=trade.is_short, refresh=False) if not fill else None
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
@ -1482,7 +1487,8 @@ class FreqtradeBot(LoggingMixin):
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side)
current_rate = self.exchange.get_rate(
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"

View File

@ -171,7 +171,7 @@ class RPC:
if trade.is_open:
try:
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=trade.exit_side)
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (ExchangeError, PricingError):
current_rate = NAN
else:
@ -231,7 +231,7 @@ class RPC:
# calculate profit and send message to user
try:
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=trade.exit_side)
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (PricingError, ExchangeError):
current_rate = NAN
trade_profit = trade.calc_profit(current_rate)
@ -485,7 +485,7 @@ class RPC:
# Get current rate
try:
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=trade.exit_side)
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
except (PricingError, ExchangeError):
current_rate = NAN
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
@ -705,7 +705,7 @@ class RPC:
if not fully_canceled:
# Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=trade.exit_side)
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL)
order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forceexit", self._freqtrade.strategy.order_types["exit"])