Update pricing to use entry/exit pricing
This commit is contained in:
parent
9f863369bd
commit
f70166270d
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"])
|
||||
|
Loading…
Reference in New Issue
Block a user