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:
|
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_min = ask_strategy.get('order_book_min')
|
||||||
ob_max = ask_strategy.get('order_book_max')
|
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 is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||||
if ob_min != ob_max:
|
if ob_min != ob_max:
|
||||||
raise OperationalException(
|
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."
|
"Please pick one value and use `order_book_top` in the future."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -208,7 +208,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
|
"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'."
|
"Please migrate your settings to use 'entry_pricing' and 'exit_pricing'."
|
||||||
)
|
)
|
||||||
conf['entry_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)
|
process_deprecated_setting(conf, 'bid_strategy', obj, 'entry_pricing', obj)
|
||||||
del conf['bid_strategy']
|
del conf['bid_strategy']
|
||||||
conf['exit_pricing'] = {}
|
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)
|
process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj)
|
||||||
del conf['ask_strategy']
|
del conf['ask_strategy']
|
||||||
|
@ -111,8 +111,8 @@ class Exchange:
|
|||||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
# 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
|
# Caching only applies to RPC methods, so prices for open trades are still
|
||||||
# refreshed once every iteration.
|
# refreshed once every iteration.
|
||||||
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
|
|
||||||
# Holds candles
|
# Holds candles
|
||||||
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
|
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
|
||||||
@ -1438,7 +1438,8 @@ class Exchange:
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from 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
|
Calculates bid/ask target
|
||||||
bid rate - between current ask price and last price
|
bid rate - between current ask price and last price
|
||||||
@ -1450,9 +1451,10 @@ class Exchange:
|
|||||||
:return: float: Price
|
:return: float: Price
|
||||||
:raises PricingError if orderbook price could not be determined.
|
:raises PricingError if orderbook price could not be determined.
|
||||||
"""
|
"""
|
||||||
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
|
name = side.capitalize()
|
||||||
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
|
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:
|
if not refresh:
|
||||||
rate = cache_rate.get(pair)
|
rate = cache_rate.get(pair)
|
||||||
# Check if cache has been invalidated
|
# Check if cache has been invalidated
|
||||||
@ -1462,27 +1464,43 @@ class Exchange:
|
|||||||
|
|
||||||
conf_strategy = self._config.get(strat_name, {})
|
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_top = conf_strategy.get('order_book_top', 1)
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
||||||
logger.debug('order_book %s', order_book)
|
logger.debug('order_book %s', order_book)
|
||||||
# top 1 = index 0
|
# top 1 = index 0
|
||||||
try:
|
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:
|
except (IndexError, KeyError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{name} Price at location {order_book_top} from orderbook could not be "
|
f"{name} Price at location {order_book_top} from orderbook could not be "
|
||||||
f"determined. Orderbook: {order_book}"
|
f"determined. Orderbook: {order_book}"
|
||||||
)
|
)
|
||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
price_side = {conf_strategy['price_side'].capitalize()}
|
logger.debug(f"{name} price from orderbook {price_side_word}"
|
||||||
logger.debug(f"{name} price from orderbook {price_side}"
|
|
||||||
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
||||||
else:
|
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 = self.fetch_ticker(pair)
|
||||||
ticker_rate = ticker[conf_strategy['price_side']]
|
ticker_rate = ticker[price_side]
|
||||||
if ticker['last'] and ticker_rate:
|
if ticker['last'] and ticker_rate:
|
||||||
if side == 'buy' and ticker_rate > ticker['last']:
|
if side == 'buy' and ticker_rate > ticker['last']:
|
||||||
balance = conf_strategy.get('ask_last_balance', 0.0)
|
balance = conf_strategy.get('ask_last_balance', 0.0)
|
||||||
|
@ -7,7 +7,7 @@ import traceback
|
|||||||
from datetime import datetime, time, timezone
|
from datetime import datetime, time, timezone
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from threading import Lock
|
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
|
from schedule import Scheduler
|
||||||
|
|
||||||
@ -511,7 +511,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.debug("Max adjustment entries is set to unlimited.")
|
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)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
|
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']
|
time_in_force = self.strategy.order_time_in_force['entry']
|
||||||
|
|
||||||
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
|
[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
|
pos_adjust = trade is not None
|
||||||
|
|
||||||
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
|
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:
|
if not stake_amount:
|
||||||
return False
|
return False
|
||||||
@ -745,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def get_valid_enter_price_and_stake(
|
def get_valid_enter_price_and_stake(
|
||||||
self, pair: str, price: Optional[float], stake_amount: float,
|
self, pair: str, price: Optional[float], stake_amount: float,
|
||||||
side: str, trade_side: str,
|
trade_side: Literal['long', 'short'],
|
||||||
entry_tag: Optional[str],
|
entry_tag: Optional[str],
|
||||||
trade: Optional[Trade]
|
trade: Optional[Trade]
|
||||||
) -> Tuple[float, float, float]:
|
) -> Tuple[float, float, float]:
|
||||||
@ -754,7 +755,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
enter_limit_requested = price
|
enter_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# 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,
|
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||||
default_retval=proposed_enter_rate)(
|
default_retval=proposed_enter_rate)(
|
||||||
pair=pair, current_time=datetime.now(timezone.utc),
|
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)
|
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||||
|
|
||||||
if not enter_limit_requested:
|
if not enter_limit_requested:
|
||||||
raise PricingError(f'Could not determine {side} price.')
|
raise PricingError('Could not determine entry price.')
|
||||||
|
|
||||||
if trade is None:
|
if trade is None:
|
||||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||||
@ -824,7 +826,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
current_rate = trade.open_rate_requested
|
current_rate = trade.open_rate_requested
|
||||||
if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
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 = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a entry order cancel occurred.
|
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_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
|
||||||
msg = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
@ -935,7 +939,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug('checking exit')
|
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):
|
if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -1433,7 +1438,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
# Use cached rates here - it was updated seconds ago.
|
# Use cached rates here - it was updated seconds ago.
|
||||||
current_rate = self.exchange.get_rate(
|
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)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
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_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
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)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ class RPC:
|
|||||||
if trade.is_open:
|
if trade.is_open:
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (ExchangeError, PricingError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
else:
|
else:
|
||||||
@ -231,7 +231,7 @@ class RPC:
|
|||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_profit = trade.calc_profit(current_rate)
|
trade_profit = trade.calc_profit(current_rate)
|
||||||
@ -485,7 +485,7 @@ class RPC:
|
|||||||
# Get current rate
|
# Get current rate
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
||||||
@ -705,7 +705,7 @@ class RPC:
|
|||||||
if not fully_canceled:
|
if not fully_canceled:
|
||||||
# Get current rate and execute sell
|
# Get current rate and execute sell
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
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)
|
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL)
|
||||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||||
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
||||||
|
Loading…
Reference in New Issue
Block a user