From d0d0cbe1d18cd03693ff15837828710537e50964 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Mar 2023 10:37:18 +0200 Subject: [PATCH] Implement price_to_precision logic for stoploss --- freqtrade/exchange/__init__.py | 18 ++++++++-------- freqtrade/exchange/exchange.py | 31 ++++++++++++++-------------- freqtrade/exchange/exchange_utils.py | 10 +++++---- freqtrade/exchange/kraken.py | 6 ++++-- freqtrade/freqtradebot.py | 6 ++++-- freqtrade/persistence/trade_model.py | 6 ++++-- 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index b815fb3ee..df10e40e5 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -8,15 +8,15 @@ from freqtrade.exchange.bitpanda import Bitpanda from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.bybit import Bybit from freqtrade.exchange.coinbasepro import Coinbasepro -from freqtrade.exchange.exchange_utils import (amount_to_contract_precision, amount_to_contracts, - amount_to_precision, available_exchanges, - ccxt_exchanges, contracts_to_amount, - date_minus_candles, is_exchange_known_ccxt, - market_is_active, price_to_precision, - timeframe_to_minutes, timeframe_to_msecs, - timeframe_to_next_date, timeframe_to_prev_date, - timeframe_to_seconds, validate_exchange, - validate_exchanges) +from freqtrade.exchange.exchange_utils import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision, + amount_to_contracts, amount_to_precision, + available_exchanges, ccxt_exchanges, + contracts_to_amount, date_minus_candles, + is_exchange_known_ccxt, market_is_active, + price_to_precision, timeframe_to_minutes, + timeframe_to_msecs, timeframe_to_next_date, + timeframe_to_prev_date, timeframe_to_seconds, + validate_exchange, validate_exchanges) from freqtrade.exchange.gate import Gate from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.huobi import Huobi diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1a5488b0f..24d3d97f7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -19,9 +19,9 @@ from ccxt import TICK_SIZE from dateutil import parser from pandas import DataFrame, concat -from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, DEFAULT_PRICE_ROUND_MODE, - NON_OPEN_EXCHANGE_STATES, BidAsk, BuySell, Config, EntryExit, - ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe) +from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk, + BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker, + OBLiteral, PairWithTimeframe) from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.enums.pricetype import PriceType @@ -30,13 +30,14 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFun RetryableOrderError, TemporaryError) from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_credentials, retrier, retrier_async) -from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contract_precision, - amount_to_contracts, amount_to_precision, - contracts_to_amount, date_minus_candles, - is_exchange_known_ccxt, market_is_active, - price_to_precision, timeframe_to_minutes, - timeframe_to_msecs, timeframe_to_next_date, - timeframe_to_prev_date, timeframe_to_seconds) +from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType, + amount_to_contract_precision, amount_to_contracts, + amount_to_precision, contracts_to_amount, + date_minus_candles, is_exchange_known_ccxt, + market_is_active, price_to_precision, + timeframe_to_minutes, timeframe_to_msecs, + timeframe_to_next_date, timeframe_to_prev_date, + timeframe_to_seconds) from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json, safe_value_fallback2) @@ -730,11 +731,11 @@ class Exchange: """ return amount_to_precision(amount, self.get_precision_amount(pair), self.precisionMode) - def price_to_precision(self, pair: str, price: float, rounding_mode: int) -> float: + def price_to_precision(self, pair: str, price: float, rounding_mode: int = ROUND) -> float: """ Returns the price rounded to the precision the Exchange accepts. The default price_rounding_mode in conf is ROUND. - Must use ROUND_UP / ROUND_DOWN for stoploss calculations. + For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts. """ return price_to_precision(price, self.get_precision_price(pair), self.precisionMode, rounding_mode) @@ -1177,12 +1178,12 @@ class Exchange: user_order_type = order_types.get('stoploss', 'market') ordertype, user_order_type = self._get_stop_order_type(user_order_type) - - stop_price_norm = self.price_to_precision(pair, stop_price) + round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP + stop_price_norm = self.price_to_precision(pair, stop_price, round_mode) limit_rate = None if user_order_type == 'limit': limit_rate = self._get_stop_limit_rate(stop_price, order_types, side) - limit_rate = self.price_to_precision(pair, limit_rate) + limit_rate = self.price_to_precision(pair, limit_rate, round_mode) if self._config['dry_run']: dry_order = self.create_dry_run_order( diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index cf2b7986d..8d6bb3838 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -224,19 +224,21 @@ def price_to_precision( price: float, price_precision: Optional[float], precisionMode: Optional[int], - rounding_mode: int = ROUND_UP, + rounding_mode: int = ROUND, ) -> float: """ - Returns the price rounded up to the precision the Exchange accepts. + Returns the price rounded to the precision the Exchange accepts. Partial Re-implementation of ccxt internal method decimal_to_precision(), - which does not support rounding up + which does not support rounding up. + For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts. + TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and align with amount_to_precision(). :param price: price to convert :param price_precision: price precision to use. Used from markets[pair]['precision']['price'] :param precisionMode: precision mode to use. Should be used from precisionMode one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE - :param rounding_mode: rounding mode to use. Defaults to ROUND_UP + :param rounding_mode: rounding mode to use. Defaults to ROUND :return: price rounded up to the precision the Exchange accepts """ if price_precision is not None and precisionMode is not None: diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index b1a19fa69..e2bcd9a90 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -12,6 +12,7 @@ from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, Invali OperationalException, TemporaryError) from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier +from freqtrade.exchange.exchange_utils import ROUND_DOWN, ROUND_UP from freqtrade.exchange.types import Tickers @@ -109,6 +110,7 @@ class Kraken(Exchange): if self.trading_mode == TradingMode.FUTURES: params.update({'reduceOnly': True}) + round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP if order_types.get('stoploss', 'market') == 'limit': ordertype = "stop-loss-limit" limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) @@ -116,11 +118,11 @@ class Kraken(Exchange): limit_rate = stop_price * limit_price_pct else: limit_rate = stop_price * (2 - limit_price_pct) - params['price2'] = self.price_to_precision(pair, limit_rate) + params['price2'] = self.price_to_precision(pair, limit_rate, round_mode) else: ordertype = "stop-loss" - stop_price = self.price_to_precision(pair, stop_price) + stop_price = self.price_to_precision(pair, stop_price, round_mode) if self._config['dry_run']: dry_order = self.create_dry_run_order( diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4482f37bf..da56e9a39 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,7 +21,8 @@ from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode, State, TradingMode) from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) -from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds +from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, timeframe_to_minutes, timeframe_to_next_date, + timeframe_to_seconds) from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.mixins import LoggingMixin from freqtrade.persistence import Order, PairLocks, Trade, init_db @@ -1230,7 +1231,8 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation) + stoploss_norm = self.exchange.price_to_precision( + trade.pair, trade.stoploss_or_liquidation, ROUND_DOWN if trade.is_short else ROUND_UP) if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side): # we check if the update is necessary diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 17117d436..c9fdf745b 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -15,7 +15,8 @@ from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPE BuySell, LongShort) from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.exchange import amount_to_contract_precision, price_to_precision +from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision, + price_to_precision) from freqtrade.leverage import interest from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.util import FtPrecise @@ -597,7 +598,8 @@ class LocalTrade(): """ Method used internally to set self.stop_loss. """ - stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode) + stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode, + ROUND_DOWN if self.is_short else ROUND_UP) if not self.stop_loss: self.initial_stop_loss = stop_loss_norm self.stop_loss = stop_loss_norm