Move get_sell_rate to exchange class

This commit is contained in:
Matthias
2021-06-02 11:39:18 +02:00
parent 12916243ec
commit bd1984386e
7 changed files with 172 additions and 177 deletions

View File

@@ -88,6 +88,10 @@ class Exchange:
# Cache for 10 minutes ...
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=1, ttl=60 * 10)
# 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)
# Holds candles
@@ -912,6 +916,15 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1,
order_book_min: int = 1):
"""
Helper generator to query orderbook in loop (used for early sell-order placing)
"""
order_book = self.fetch_l2_order_book(pair, order_book_max)
for i in range(order_book_min, order_book_max + 1):
yield order_book[side][i - 1][0]
def get_buy_rate(self, pair: str, refresh: bool) -> float:
"""
Calculates bid target between current ask price and last price
@@ -958,6 +971,46 @@ class Exchange:
return used_rate
def get_sell_rate(self, pair: str, refresh: bool) -> float:
"""
Get sell rate - either using ticker bid or first bid based on orderbook
or remain static in any other case since it's not updating.
:param pair: Pair to get rate for
:param refresh: allow cached data
:return: Bid rate
:raises PricingError if price could not be determined.
"""
if not refresh:
rate = self._sell_rate_cache.get(pair)
# Check if cache has been invalidated
if rate:
logger.debug(f"Using cached sell rate for {pair}.")
return rate
ask_strategy = self._config.get('ask_strategy', {})
if ask_strategy.get('use_order_book', False):
# This code is only used for notifications, selling uses the generator directly
logger.info(
f"Getting price from order book {ask_strategy['price_side'].capitalize()} side."
)
try:
rate = next(self._order_book_gen(pair, f"{ask_strategy['price_side']}s"))
except (IndexError, KeyError) as e:
logger.warning("Sell Price at location from orderbook could not be determined.")
raise PricingError from e
else:
ticker = self.fetch_ticker(pair)
ticker_rate = ticker[ask_strategy['price_side']]
if ticker['last'] and ticker_rate < ticker['last']:
balance = ask_strategy.get('bid_last_balance', 0.0)
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
rate = ticker_rate
if rate is None:
raise PricingError(f"Sell-Rate for {pair} was empty.")
self._sell_rate_cache[pair] = rate
return rate
# Fee handling
@retrier

View File

@@ -10,7 +10,6 @@ from threading import Lock
from typing import Any, Dict, List, Optional
import arrow
from cachetools import TTLCache
from freqtrade import __version__, constants
from freqtrade.configuration import validate_config_consistency
@@ -58,11 +57,6 @@ class FreqtradeBot(LoggingMixin):
# Init objects
self.config = config
# 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.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
# Check config consistency here since strategies can set certain options
@@ -395,7 +389,6 @@ class FreqtradeBot(LoggingMixin):
return trades_created
def create_trade(self, pair: str) -> bool:
"""
Check the implemented trading strategy for buy signals.
@@ -678,56 +671,6 @@ class FreqtradeBot(LoggingMixin):
return trades_closed
def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1,
order_book_min: int = 1):
"""
Helper generator to query orderbook in loop (used for early sell-order placing)
"""
order_book = self.exchange.fetch_l2_order_book(pair, order_book_max)
for i in range(order_book_min, order_book_max + 1):
yield order_book[side][i - 1][0]
def get_sell_rate(self, pair: str, refresh: bool) -> float:
"""
Get sell rate - either using ticker bid or first bid based on orderbook
The orderbook portion is only used for rpc messaging, which would otherwise fail
for BitMex (has no bid/ask in fetch_ticker)
or remain static in any other case since it's not updating.
:param pair: Pair to get rate for
:param refresh: allow cached data
:return: Bid rate
"""
if not refresh:
rate = self._sell_rate_cache.get(pair)
# Check if cache has been invalidated
if rate:
logger.debug(f"Using cached sell rate for {pair}.")
return rate
ask_strategy = self.config.get('ask_strategy', {})
if ask_strategy.get('use_order_book', False):
# This code is only used for notifications, selling uses the generator directly
logger.info(
f"Getting price from order book {ask_strategy['price_side'].capitalize()} side."
)
try:
rate = next(self._order_book_gen(pair, f"{ask_strategy['price_side']}s"))
except (IndexError, KeyError) as e:
logger.warning("Sell Price at location from orderbook could not be determined.")
raise PricingError from e
else:
ticker = self.exchange.fetch_ticker(pair)
ticker_rate = ticker[ask_strategy['price_side']]
if ticker['last'] and ticker_rate < ticker['last']:
balance = ask_strategy.get('bid_last_balance', 0.0)
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
rate = ticker_rate
if rate is None:
raise PricingError(f"Sell-Rate for {pair} was empty.")
self._sell_rate_cache[pair] = rate
return rate
def handle_trade(self, trade: Trade) -> bool:
"""
Sells the current pair if the threshold is reached and updates the trade record.
@@ -755,9 +698,9 @@ class FreqtradeBot(LoggingMixin):
logger.debug(f'Using order book between {order_book_min} and {order_book_max} '
f'for selling {trade.pair}...')
order_book = self._order_book_gen(trade.pair, f"{config_ask_strategy['price_side']}s",
order_book_min=order_book_min,
order_book_max=order_book_max)
order_book = self.exchange._order_book_gen(
trade.pair, f"{config_ask_strategy['price_side']}s",
order_book_min=order_book_min, order_book_max=order_book_max)
for i in range(order_book_min, order_book_max + 1):
try:
sell_rate = next(order_book)
@@ -770,14 +713,14 @@ class FreqtradeBot(LoggingMixin):
f"{sell_rate:0.8f}")
# Assign sell-rate to cache - otherwise sell-rate is never updated in the cache,
# resulting in outdated RPC messages
self._sell_rate_cache[trade.pair] = sell_rate
self.exchange._sell_rate_cache[trade.pair] = sell_rate
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
else:
logger.debug('checking sell')
sell_rate = self.get_sell_rate(trade.pair, True)
sell_rate = self.exchange.get_sell_rate(trade.pair, True)
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
@@ -1209,7 +1152,7 @@ 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)
# Use cached rates here - it was updated seconds ago.
current_rate = self.get_sell_rate(trade.pair, False) if not fill else None
current_rate = self.exchange.get_sell_rate(trade.pair, False) if not fill else None
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
@@ -1254,7 +1197,7 @@ 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.get_sell_rate(trade.pair, False)
current_rate = self.exchange.get_sell_rate(trade.pair, 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:
# calculate profit and send message to user
if trade.is_open:
try:
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
except (ExchangeError, PricingError):
current_rate = NAN
else:
@@ -230,7 +230,7 @@ class RPC:
for trade in trades:
# calculate profit and send message to user
try:
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
except (PricingError, ExchangeError):
current_rate = NAN
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
@@ -386,7 +386,7 @@ class RPC:
else:
# Get current rate
try:
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
except (PricingError, ExchangeError):
current_rate = NAN
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
@@ -556,7 +556,7 @@ class RPC:
if not fully_canceled:
# Get current rate and execute sell
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
# ---- EOF def _exec_forcesell ----