From 0e61c2d05717617dc80df252af5bfc5da60238bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Aug 2022 20:31:03 +0200 Subject: [PATCH 01/26] Replace Decimal with FtPrecise in trade_model --- freqtrade/persistence/trade_model.py | 26 +++++++++++++------------- tests/exchange/test_ccxt_precise.py | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 3ea6ddf2d..4797ba99d 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -3,7 +3,6 @@ This module contains the class to persist trades into SQLite """ import logging from datetime import datetime, timedelta, timezone -from decimal import Decimal from math import isclose from typing import Any, Dict, List, Optional @@ -17,6 +16,7 @@ from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.leverage import interest from freqtrade.persistence.base import _DECL_BASE +from freqtrade.util import FtPrecise logger = logging.getLogger(__name__) @@ -694,8 +694,8 @@ class LocalTrade(): Calculate the open_rate including open_fee. :return: Price in of the open trade incl. Fees """ - open_trade = Decimal(amount) * Decimal(open_rate) - fees = open_trade * Decimal(self.fee_open) + open_trade = FtPrecise(amount) * FtPrecise(open_rate) + fees = open_trade * FtPrecise(self.fee_open) if self.is_short: return float(open_trade - fees) else: @@ -708,30 +708,30 @@ class LocalTrade(): """ self.open_trade_value = self._calc_open_trade_value(self.amount, self.open_rate) - def calculate_interest(self) -> Decimal: + def calculate_interest(self) -> FtPrecise: """ Calculate interest for this trade. Only applicable for Margin trading. """ - zero = Decimal(0.0) + zero = FtPrecise(0.0) # If nothing was borrowed if self.trading_mode != TradingMode.MARGIN or self.has_no_leverage: return zero open_date = self.open_date.replace(tzinfo=None) now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None) - sec_per_hour = Decimal(3600) - total_seconds = Decimal((now - open_date).total_seconds()) + sec_per_hour = FtPrecise(3600) + total_seconds = FtPrecise((now - open_date).total_seconds()) hours = total_seconds / sec_per_hour or zero - rate = Decimal(self.interest_rate) - borrowed = Decimal(self.borrowed) + rate = FtPrecise(self.interest_rate) + borrowed = FtPrecise(self.borrowed) return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours) - def _calc_base_close(self, amount: Decimal, rate: float, fee: float) -> Decimal: + def _calc_base_close(self, amount: FtPrecise, rate: float, fee: float) -> FtPrecise: - close_trade = amount * Decimal(rate) - fees = close_trade * Decimal(fee) + close_trade = amount * FtPrecise(rate) + fees = close_trade * FtPrecise(fee) if self.is_short: return close_trade + fees @@ -747,7 +747,7 @@ class LocalTrade(): if rate is None and not self.close_rate: return 0.0 - amount1 = Decimal(amount or self.amount) + amount1 = FtPrecise(amount or self.amount) trading_mode = self.trading_mode or TradingMode.SPOT if trading_mode == TradingMode.SPOT: diff --git a/tests/exchange/test_ccxt_precise.py b/tests/exchange/test_ccxt_precise.py index 8b599093f..5542ac8d2 100644 --- a/tests/exchange/test_ccxt_precise.py +++ b/tests/exchange/test_ccxt_precise.py @@ -78,3 +78,5 @@ def test_FtPrecise(): assert FtPrecise(-213) == '-213' assert str(FtPrecise(-213)) == '-213' assert FtPrecise(213.2) == '213.2' + assert float(FtPrecise(213.2)) == 213.2 + assert float(FtPrecise(-213.2)) == -213.2 From da253f12fe598a5285c6f3c462b585d8248289d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Aug 2022 20:45:54 +0200 Subject: [PATCH 02/26] Bump CCXT to required version --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4fa6e60a0..06c3ca44f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.23.1 pandas==1.4.3 pandas-ta==0.3.14b -ccxt==1.91.93 +ccxt==1.92.9 # Pin cryptography for now due to rust build errors with piwheels cryptography==37.0.4 aiohttp==3.8.1 diff --git a/setup.py b/setup.py index 7aa56bf81..95671d3b8 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.83.12', + 'ccxt>=1.92.9', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', From 902afc2f027de1c269925401c5a6b1f5d1b05f8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Aug 2022 20:56:04 +0200 Subject: [PATCH 03/26] Use FtPrecise in interest calculation --- freqtrade/leverage/interest.py | 22 +++++++++++----------- tests/leverage/test_interest.py | 12 ++++++------ tests/rpc/test_rpc_apiserver.py | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/freqtrade/leverage/interest.py b/freqtrade/leverage/interest.py index 367df5821..ddeea2b42 100644 --- a/freqtrade/leverage/interest.py +++ b/freqtrade/leverage/interest.py @@ -1,20 +1,20 @@ -from decimal import Decimal from math import ceil from freqtrade.exceptions import OperationalException +from freqtrade.util import FtPrecise -one = Decimal(1.0) -four = Decimal(4.0) -twenty_four = Decimal(24.0) +one = FtPrecise(1.0) +four = FtPrecise(4.0) +twenty_four = FtPrecise(24.0) def interest( exchange_name: str, - borrowed: Decimal, - rate: Decimal, - hours: Decimal -) -> Decimal: + borrowed: FtPrecise, + rate: FtPrecise, + hours: FtPrecise +) -> FtPrecise: """ Equation to calculate interest on margin trades @@ -31,13 +31,13 @@ def interest( """ exchange_name = exchange_name.lower() if exchange_name == "binance": - return borrowed * rate * ceil(hours) / twenty_four + return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four elif exchange_name == "kraken": # Rounded based on https://kraken-fees-calculator.github.io/ - return borrowed * rate * (one + ceil(hours / four)) + return borrowed * rate * (one + FtPrecise(ceil(hours / four))) elif exchange_name == "ftx": # As Explained under #Interest rates section in # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer - return borrowed * rate * ceil(hours) / twenty_four + return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four else: raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade") diff --git a/tests/leverage/test_interest.py b/tests/leverage/test_interest.py index 6b189ce50..7bdf4c2f0 100644 --- a/tests/leverage/test_interest.py +++ b/tests/leverage/test_interest.py @@ -1,14 +1,14 @@ -from decimal import Decimal from math import isclose import pytest from freqtrade.leverage import interest +from freqtrade.util import FtPrecise -ten_mins = Decimal(1 / 6) -five_hours = Decimal(5.0) -twentyfive_hours = Decimal(25.0) +ten_mins = FtPrecise(1 / 6) +five_hours = FtPrecise(5.0) +twentyfive_hours = FtPrecise(25.0) @pytest.mark.parametrize('exchange,interest_rate,hours,expected', [ @@ -28,11 +28,11 @@ twentyfive_hours = Decimal(25.0) ('ftx', 0.00025, twentyfive_hours, 0.015625), ]) def test_interest(exchange, interest_rate, hours, expected): - borrowed = Decimal(60.0) + borrowed = FtPrecise(60.0) assert isclose(interest( exchange_name=exchange, borrowed=borrowed, - rate=Decimal(interest_rate), + rate=FtPrecise(interest_rate), hours=hours ), expected) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 25343ead6..eb3172704 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -892,7 +892,7 @@ def test_api_performance(botclient, fee): assert_response(rc) assert len(rc.json()) == 2 assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61, - 'profit_ratio': 0.07609203, 'profit_abs': 0.01872279}, + 'profit_ratio': 0.07609203, 'profit_abs': 0.0187228}, {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57, 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}] From 3bcb47d75d33d57885babee10bbac1c3a8af8e1c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 Aug 2022 06:34:12 +0200 Subject: [PATCH 04/26] Remove usage of Decimal --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0dbeb2e44..e3ef01dd5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -5,7 +5,6 @@ import copy import logging import traceback from datetime import datetime, time, timedelta, timezone -from decimal import Decimal from math import isclose from threading import Lock from typing import Any, Dict, List, Optional, Tuple @@ -33,6 +32,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper +from freqtrade.util import FtPrecise from freqtrade.wallets import Wallets @@ -565,7 +565,7 @@ class FreqtradeBot(LoggingMixin): if stake_amount is not None and stake_amount < 0.0: # We should decrease our position - amount = abs(float(Decimal(stake_amount) / Decimal(current_exit_rate))) + amount = abs(float(FtPrecise(stake_amount) / FtPrecise(current_exit_rate))) if amount > trade.amount: # This is currently ineffective as remaining would become < min tradable # Fixing this would require checking for 0.0 there - From 9513c39a171a16df17250bb4f0571d4cd50b357b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 Aug 2022 06:38:14 +0200 Subject: [PATCH 05/26] Fix migration rounding test --- tests/test_persistence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index eea6d6fa1..8a3a18577 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1387,7 +1387,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert log_has("trying trades_bak2", caplog) assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0", caplog) - assert trade.open_trade_value == trade._calc_open_trade_value(trade.amount, trade.open_rate) + assert round(trade.open_trade_value, 15) == trade._calc_open_trade_value( + trade.amount, trade.open_rate) assert trade.close_profit_abs is None orders = trade.orders From e3a5b97b451c0a59eec8df4dfc60a5de9c36e4cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 11 Aug 2022 06:51:34 +0200 Subject: [PATCH 06/26] Update recalc_from_trades to use FtPrecise --- freqtrade/persistence/trade_model.py | 33 ++++++++++++++-------------- tests/test_integration.py | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 4797ba99d..faed1eab5 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -827,11 +827,11 @@ class LocalTrade(): return float(f"{profit_ratio:.8f}") def recalc_trade_from_orders(self, is_closing: bool = False): - - current_amount = 0.0 - current_stake = 0.0 + ZERO = FtPrecise(0.0) + current_amount = FtPrecise(0.0) + current_stake = FtPrecise(0.0) total_stake = 0.0 # Total stake after all buy orders (does not subtract!) - avg_price = 0.0 + avg_price = FtPrecise(0.0) close_profit = 0.0 close_profit_abs = 0.0 @@ -839,28 +839,29 @@ class LocalTrade(): if o.ft_is_open or not o.filled: continue - tmp_amount = o.safe_amount_after_fee - tmp_price = o.safe_price + tmp_amount = FtPrecise(o.safe_amount_after_fee) + tmp_price = FtPrecise(o.safe_price) is_exit = o.ft_order_side != self.entry_side - side = -1 if is_exit else 1 - if tmp_amount > 0.0 and tmp_price is not None: + side = FtPrecise(-1 if is_exit else 1) + if tmp_amount > ZERO and tmp_price is not None: current_amount += tmp_amount * side price = avg_price if is_exit else tmp_price current_stake += price * tmp_amount * side - if current_amount > 0: + if current_amount > ZERO: avg_price = current_stake / current_amount if is_exit: # Process partial exits exit_rate = o.safe_price exit_amount = o.safe_amount_after_fee - profit = self.calc_profit(rate=exit_rate, amount=exit_amount, open_rate=avg_price) + profit = self.calc_profit(rate=exit_rate, amount=exit_amount, + open_rate=float(avg_price)) close_profit_abs += profit close_profit = self.calc_profit_ratio( exit_rate, amount=exit_amount, open_rate=avg_price) - if current_amount <= 0: + if current_amount <= ZERO: profit = close_profit_abs else: total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price) @@ -870,13 +871,13 @@ class LocalTrade(): self.realized_profit = close_profit_abs self.close_profit_abs = profit - if current_amount > 0: + if current_amount > ZERO: # Trade is still open # Leverage not updated, as we don't allow changing leverage through DCA at the moment. - self.open_rate = current_stake / current_amount - self.stake_amount = current_stake / (self.leverage or 1.0) - self.amount = current_amount - self.fee_open_cost = self.fee_open * current_stake + self.open_rate = float(current_stake / current_amount) + self.stake_amount = float(current_stake) / (self.leverage or 1.0) + self.amount = float(current_amount) + self.fee_open_cost = self.fee_open * float(current_stake) self.recalc_open_trade_value() if self.stop_loss_pct is not None and self.open_rate is not None: self.adjust_stop_loss(self.open_rate, self.stop_loss_pct) diff --git a/tests/test_integration.py b/tests/test_integration.py index 6a11b13f4..b970e1c89 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -189,7 +189,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati assert len(trades) == 5 for trade in trades: - assert trade.stake_amount == result1 + assert pytest.approx(trade.stake_amount) == result1 # Reset trade open order id's trade.open_order_id = None trades = Trade.get_open_trades() From 7a2b4dbb990b1c3abf022c2809c46965508d87fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 13 Aug 2022 20:16:36 +0200 Subject: [PATCH 07/26] Fix docs edit button --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index a43322f78..45f9aba27 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,7 @@ site_name: Freqtrade site_url: https://www.freqtrade.io/ repo_url: https://github.com/freqtrade/freqtrade +edit_uri: edit/develop/docs/ use_directory_urls: True nav: - Home: index.md From 6c32331740101cdf5c1b21b4952a47f761ba4d2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 08:43:58 +0200 Subject: [PATCH 08/26] Move precision calculations to standalone functions --- freqtrade/exchange/exchange.py | 90 +++++++++++++++++++--------- freqtrade/persistence/trade_model.py | 2 +- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 18598e92d..67423bd9b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -683,42 +683,18 @@ class Exchange: def amount_to_precision(self, pair: str, amount: float) -> float: """ Returns the amount to buy or sell to a precision the Exchange accepts - Re-implementation of ccxt internal methods - ensuring we can test the result is correct - based on our definitions. - """ - if self.markets[pair]['precision']['amount'] is not None: - amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, - precision=self.markets[pair]['precision']['amount'], - counting_mode=self.precisionMode, - )) - return amount + """ + return amount_to_precision(amount, self.markets[pair]['precision']['amount'], + self.precisionMode) def price_to_precision(self, pair: str, price: float) -> float: """ Returns the price rounded up to the precision the Exchange accepts. - Partial Re-implementation of ccxt internal method decimal_to_precision(), - which does not support rounding up - TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and - align with amount_to_precision(). Rounds up """ - if self.markets[pair]['precision']['price']: - # price = float(decimal_to_precision(price, rounding_mode=ROUND, - # precision=self.markets[pair]['precision']['price'], - # counting_mode=self.precisionMode, - # )) - if self.precisionMode == TICK_SIZE: - precision = FtPrecise(self.markets[pair]['precision']['price']) - price_str = FtPrecise(price) - missing = price_str % precision - if not missing == FtPrecise("0"): - price = round(float(str(price_str - missing + precision)), 14) - else: - symbol_prec = self.markets[pair]['precision']['price'] - big_price = price * pow(10, symbol_prec) - price = ceil(big_price) / pow(10, symbol_prec) - return price + return price_to_precision(price, self.markets[pair]['precision']['price'], + self.precisionMode) def price_get_one_pip(self, pair: str, price: float) -> float: """ @@ -2849,3 +2825,59 @@ def market_is_active(market: Dict) -> bool: # See https://github.com/ccxt/ccxt/issues/4874, # https://github.com/ccxt/ccxt/issues/4075#issuecomment-434760520 return market.get('active', True) is not False + + +def amount_to_precision(amount: float, amount_precision: Optional[float], + precisionMode: int) -> float: + """ + Returns the amount to buy or sell to a precision the Exchange accepts + Re-implementation of ccxt internal methods - ensuring we can test the result is correct + based on our definitions. + :param amount: amount to truncate + :param amount_precision: amount precision to use. + should be retrieved from markets[pair]['precision']['amount'] + :param precisionMode: precision mode to use. Should be used from precisionMode + one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE + :return: truncated amount + """ + if amount_precision is not None: + amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, + precision=amount_precision, + counting_mode=precisionMode, + )) + + return amount + + +def price_to_precision(price: float, price_precision: Optional[float], + precisionMode: int) -> float: + """ + Returns the price rounded up to the precision the Exchange accepts. + Partial Re-implementation of ccxt internal method decimal_to_precision(), + which does not support rounding up + TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and + align with amount_to_precision(). + !!! Rounds up + :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 + :return: price rounded up to the precision the Exchange accepts + + """ + if price_precision: + # price = float(decimal_to_precision(price, rounding_mode=ROUND, + # precision=price_precision, + # counting_mode=self.precisionMode, + # )) + if precisionMode == TICK_SIZE: + precision = FtPrecise(price_precision) + price_str = FtPrecise(price) + missing = price_str % precision + if not missing == FtPrecise("0"): + price = round(float(str(price_str - missing + precision)), 14) + else: + symbol_prec = price_precision + big_price = price * pow(10, symbol_prec) + price = ceil(big_price) / pow(10, symbol_prec) + return price diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index faed1eab5..864b4fde9 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -826,7 +826,7 @@ class LocalTrade(): return float(f"{profit_ratio:.8f}") - def recalc_trade_from_orders(self, is_closing: bool = False): + def recalc_trade_from_orders(self, *, is_closing: bool = False): ZERO = FtPrecise(0.0) current_amount = FtPrecise(0.0) current_stake = FtPrecise(0.0) From 15e85797c2e3df415ecfd82da942dc96909893c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 08:51:15 +0200 Subject: [PATCH 09/26] Simplify to_precision tests and imports --- freqtrade/exchange/__init__.py | 13 ++--- freqtrade/exchange/okx.py | 3 +- tests/exchange/test_exchange.py | 85 ++++++++++----------------------- 3 files changed, 33 insertions(+), 68 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 2b9ed47ea..cb63c6b9a 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -9,12 +9,13 @@ 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 import (available_exchanges, ccxt_exchanges, - is_exchange_known_ccxt, is_exchange_officially_supported, - market_is_active, 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 import (amount_to_precision, available_exchanges, ccxt_exchanges, + date_minus_candles, is_exchange_known_ccxt, + is_exchange_officially_supported, 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.ftx import Ftx from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index afd7a672f..540e76fca 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -7,9 +7,8 @@ from freqtrade.constants import BuySell from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums.candletype import CandleType from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError -from freqtrade.exchange import Exchange +from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange.common import retrier -from freqtrade.exchange.exchange import date_minus_candles logger = logging.getLogger(__name__) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bbe424430..6ad4a72c6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -14,12 +14,12 @@ from pandas import DataFrame from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, OperationalException, PricingError, TemporaryError) -from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken +from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision, + date_minus_candles, 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.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT, calculate_backoff, remove_credentials) -from freqtrade.exchange.exchange import (date_minus_candles, market_is_active, timeframe_to_minutes, - timeframe_to_msecs, timeframe_to_next_date, - timeframe_to_prev_date, timeframe_to_seconds) from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re @@ -279,62 +279,35 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -@pytest.mark.parametrize("amount,precision_mode,precision,contract_size,expected,trading_mode", [ - (2.34559, 2, 4, 1, 2.3455, 'spot'), - (2.34559, 2, 5, 1, 2.34559, 'spot'), - (2.34559, 2, 3, 1, 2.345, 'spot'), - (2.9999, 2, 3, 1, 2.999, 'spot'), - (2.9909, 2, 3, 1, 2.990, 'spot'), - (2.9909, 2, 0, 1, 2, 'spot'), - (29991.5555, 2, 0, 1, 29991, 'spot'), - (29991.5555, 2, -1, 1, 29990, 'spot'), - (29991.5555, 2, -2, 1, 29900, 'spot'), +@pytest.mark.parametrize("amount,precision_mode,precision,expected", [ + (2.34559, 2, 4, 2.3455), + (2.34559, 2, 5, 2.34559), + (2.34559, 2, 3, 2.345), + (2.9999, 2, 3, 2.999), + (2.9909, 2, 3, 2.990), + (2.9909, 2, 0, 2), + (29991.5555, 2, 0, 29991), + (29991.5555, 2, -1, 29990), + (29991.5555, 2, -2, 29900), # Tests for Tick-size - (2.34559, 4, 0.0001, 1, 2.3455, 'spot'), - (2.34559, 4, 0.00001, 1, 2.34559, 'spot'), - (2.34559, 4, 0.001, 1, 2.345, 'spot'), - (2.9999, 4, 0.001, 1, 2.999, 'spot'), - (2.9909, 4, 0.001, 1, 2.990, 'spot'), - (2.9909, 4, 0.005, 0.01, 2.99, 'futures'), - (2.9999, 4, 0.005, 10, 2.995, 'futures'), + (2.34559, 4, 0.0001, 2.3455), + (2.34559, 4, 0.00001, 2.34559), + (2.34559, 4, 0.001, 2.345), + (2.9999, 4, 0.001, 2.999), + (2.9909, 4, 0.001, 2.990), + (2.9909, 4, 0.005, 2.99), + (2.9999, 4, 0.005, 2.995), ]) -def test_amount_to_precision( - default_conf, - mocker, - amount, - precision_mode, - precision, - contract_size, - expected, - trading_mode -): +def test_amount_to_precision(amount, precision_mode, precision, expected,): """ Test rounds down """ - - markets = PropertyMock(return_value={ - 'ETH/BTC': { - 'contractSize': contract_size, - 'precision': { - 'amount': precision - } - } - }) - - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = 'isolated' - - exchange = get_patched_exchange(mocker, default_conf, id="binance") # digits counting mode # DECIMAL_PLACES = 2 # SIGNIFICANT_DIGITS = 3 # TICK_SIZE = 4 - mocker.patch('freqtrade.exchange.Exchange.precisionMode', - PropertyMock(return_value=precision_mode)) - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - pair = 'ETH/BTC' - assert exchange.amount_to_precision(pair, amount) == expected + assert amount_to_precision(amount, precision, precision_mode) == expected @pytest.mark.parametrize("price,precision_mode,precision,expected", [ @@ -359,21 +332,13 @@ def test_amount_to_precision( (0.000000003483, 4, 1e-12, 0.000000003483), ]) -def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected): - """Test price to precision""" - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}}) - - exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch('freqtrade.exchange.Exchange.markets', markets) +def test_price_to_precision(price, precision_mode, precision, expected): # digits counting mode # DECIMAL_PLACES = 2 # SIGNIFICANT_DIGITS = 3 # TICK_SIZE = 4 - mocker.patch('freqtrade.exchange.Exchange.precisionMode', - PropertyMock(return_value=precision_mode)) - pair = 'ETH/BTC' - assert exchange.price_to_precision(pair, price) == expected + assert price_to_precision(price, precision, precision_mode) == expected @pytest.mark.parametrize("price,precision_mode,precision,expected", [ From 22241c55d530dc31addbbe3f4db2280b3e33324d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 19:56:25 +0200 Subject: [PATCH 10/26] Add methods to get precision_amount from markets --- freqtrade/exchange/exchange.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 67423bd9b..1af555e5e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -680,21 +680,33 @@ class Exchange: """ return endpoint in self._api.has and self._api.has[endpoint] + def get_precision_amount(self, pair: str) -> Optional[float]: + """ + Returns the amount precision of the exchange. + :param pair: + """ + return self.markets[pair].get('precision', {}).get('amount', None) + + def get_precision_price(self, pair: str) -> Optional[float]: + """ + Returns the price precision of the exchange. + :param pair: + """ + return self.markets[pair].get('precision', {}).get('price', None) + def amount_to_precision(self, pair: str, amount: float) -> float: """ Returns the amount to buy or sell to a precision the Exchange accepts """ - return amount_to_precision(amount, self.markets[pair]['precision']['amount'], - self.precisionMode) + return amount_to_precision(amount, self.get_precision_amount(pair), self.precisionMode) def price_to_precision(self, pair: str, price: float) -> float: """ Returns the price rounded up to the precision the Exchange accepts. Rounds up """ - return price_to_precision(price, self.markets[pair]['precision']['price'], - self.precisionMode) + return price_to_precision(price, self.get_precision_price(pair), self.precisionMode) def price_get_one_pip(self, pair: str, price: float) -> float: """ From c3f159bd5749428f8e1bc0b623ffc2a747844dc7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 19:58:40 +0200 Subject: [PATCH 11/26] Add precision fields to database --- freqtrade/freqtradebot.py | 16 +++++++++++++++- freqtrade/optimize/backtesting.py | 3 +++ freqtrade/persistence/migrations.py | 13 ++++++++++--- freqtrade/persistence/trade_model.py | 13 +++++++++++-- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e3ef01dd5..8efc6ab59 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -159,6 +159,8 @@ class FreqtradeBot(LoggingMixin): performs startup tasks """ self.rpc.startup_messages(self.config, self.pairlists, self.protections) + # Update older trades with precision and precision mode + self.startup_backpopulate_precision() if not self.edge: # Adjust stoploss if it was changed Trade.stoploss_reinitialization(self.strategy.stoploss) @@ -286,6 +288,15 @@ class FreqtradeBot(LoggingMixin): else: return 0.0 + def startup_backpopulate_precision(self): + + trades = Trade.get_trades([Trade.precision_mode.is_(None)]) + for trade in trades: + trade.precision_mode = self.exchange.precisionMode + trade.amount_precision = self.exchange.get_precision_amount(trade.pair) + trade.price_precision = self.exchange.get_precision_price(trade.pair) + Trade.commit() + def startup_update_open_orders(self): """ Updates open orders based on order list kept in the database. @@ -738,7 +749,10 @@ class FreqtradeBot(LoggingMixin): leverage=leverage, is_short=is_short, trading_mode=self.trading_mode, - funding_fees=funding_fees + funding_fees=funding_fees, + amount_precision=self.exchange.get_precision_amount(pair), + price_precision=self.exchange.get_precision_price(pair), + precision_mode=self.exchange.precisionMode, ) else: # This is additional buy, we reset fee_open_currency so timeout checking can work diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 029946cfb..795d20644 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -837,6 +837,9 @@ class Backtesting: trading_mode=self.trading_mode, leverage=leverage, # interest_rate=interest_rate, + amount_precision=self.exchange.get_precision_amount(pair), + price_precision=self.exchange.get_precision_price(pair), + precision_mode=self.exchange.precisionMode, orders=[], ) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 81757a7de..e54675f16 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -130,6 +130,10 @@ def migrate_trades_and_orders_table( get_column_def(cols, 'sell_order_status', 'null')) amount_requested = get_column_def(cols, 'amount_requested', 'amount') + amount_precision = get_column_def(cols, 'amount_precision', 'null') + price_precision = get_column_def(cols, 'price_precision', 'null') + precision_mode = get_column_def(cols, 'precision_mode', 'null') + # Schema migration necessary with engine.begin() as connection: connection.execute(text(f"alter table trades rename to {trade_back_name}")) @@ -156,7 +160,8 @@ def migrate_trades_and_orders_table( max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag, timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, liquidation_price, is_short, - interest_rate, funding_fees, realized_profit + interest_rate, funding_fees, realized_profit, + amount_precision, price_precision, precision_mode ) select id, lower(exchange), pair, {base_currency} base_currency, {stake_currency} stake_currency, @@ -182,7 +187,9 @@ def migrate_trades_and_orders_table( {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, {trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price, {is_short} is_short, {interest_rate} interest_rate, - {funding_fees} funding_fees, {realized_profit} realized_profit + {funding_fees} funding_fees, {realized_profit} realized_profit, + {amount_precision} amount_precision, {price_precision} price_precision, + {precision_mode} precision_mode from {trade_back_name} """)) @@ -300,7 +307,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # Migrates both trades and orders table! # if ('orders' not in previous_tables # or not has_column(cols_orders, 'stop_price')): - if not has_column(cols_trades, 'realized_profit'): + if not has_column(cols_trades, 'precision_mode'): logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") migrate_trades_and_orders_table( diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 864b4fde9..436919bb1 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -14,6 +14,7 @@ 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_precision, price_to_precision from freqtrade.leverage import interest from freqtrade.persistence.base import _DECL_BASE from freqtrade.util import FtPrecise @@ -292,6 +293,9 @@ class LocalTrade(): timeframe: Optional[int] = None trading_mode: TradingMode = TradingMode.SPOT + amount_precision: Optional[float] = None + price_precision: Optional[float] = None + precision_mode: Optional[int] = None # Leverage trading properties liquidation_price: Optional[float] = None @@ -874,9 +878,11 @@ class LocalTrade(): if current_amount > ZERO: # Trade is still open # Leverage not updated, as we don't allow changing leverage through DCA at the moment. - self.open_rate = float(current_stake / current_amount) + self.open_rate = price_to_precision(float(current_stake / current_amount), + self.price_precision, self.precision_mode) + self.amount = amount_to_precision(float(current_amount), + self.amount_precision, self.precision_mode) self.stake_amount = float(current_stake) / (self.leverage or 1.0) - self.amount = float(current_amount) self.fee_open_cost = self.fee_open * float(current_stake) self.recalc_open_trade_value() if self.stop_loss_pct is not None and self.open_rate is not None: @@ -1120,6 +1126,9 @@ class Trade(_DECL_BASE, LocalTrade): timeframe = Column(Integer, nullable=True) trading_mode = Column(Enum(TradingMode), nullable=True) + amount_precision = Column(Float) + price_precision = Column(Float) + precision_mode = Column(Integer) # Leverage trading properties leverage = Column(Float, nullable=True, default=1.0) From e6af9a6903f4036c65ef2bfe8055c5eaf160eda3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 20:00:15 +0200 Subject: [PATCH 12/26] Allow empty precisionMode on conversions --- freqtrade/exchange/exchange.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1af555e5e..cb1e5405a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2840,7 +2840,7 @@ def market_is_active(market: Dict) -> bool: def amount_to_precision(amount: float, amount_precision: Optional[float], - precisionMode: int) -> float: + precisionMode: Optional[int]) -> float: """ Returns the amount to buy or sell to a precision the Exchange accepts Re-implementation of ccxt internal methods - ensuring we can test the result is correct @@ -2852,7 +2852,7 @@ def amount_to_precision(amount: float, amount_precision: Optional[float], one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE :return: truncated amount """ - if amount_precision is not None: + if amount_precision is not None and precisionMode is not None: amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, precision=amount_precision, counting_mode=precisionMode, @@ -2862,7 +2862,7 @@ def amount_to_precision(amount: float, amount_precision: Optional[float], def price_to_precision(price: float, price_precision: Optional[float], - precisionMode: int) -> float: + precisionMode: Optional[int]) -> float: """ Returns the price rounded up to the precision the Exchange accepts. Partial Re-implementation of ccxt internal method decimal_to_precision(), @@ -2877,7 +2877,7 @@ def price_to_precision(price: float, price_precision: Optional[float], :return: price rounded up to the precision the Exchange accepts """ - if price_precision: + if price_precision is not None and precisionMode is not None: # price = float(decimal_to_precision(price, rounding_mode=ROUND, # precision=price_precision, # counting_mode=self.precisionMode, From f2b6ff910f975beca136311816117bc79dc0a93f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 20:05:22 +0200 Subject: [PATCH 13/26] Accept wrong pair in get_precision_amount --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index cb1e5405a..8605e984a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -685,14 +685,14 @@ class Exchange: Returns the amount precision of the exchange. :param pair: """ - return self.markets[pair].get('precision', {}).get('amount', None) + return self.markets.get(pair, {}).get('precision', {}).get('amount', None) def get_precision_price(self, pair: str) -> Optional[float]: """ Returns the price precision of the exchange. :param pair: """ - return self.markets[pair].get('precision', {}).get('price', None) + return self.markets.get(pair, {}).get('precision', {}).get('price', None) def amount_to_precision(self, pair: str, amount: float) -> float: """ From c0bdb7181019940c62c7ec93aa28de6a020c1185 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 20:06:29 +0200 Subject: [PATCH 14/26] Update docstring --- freqtrade/exchange/exchange.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8605e984a..7629b1e01 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -683,14 +683,16 @@ class Exchange: def get_precision_amount(self, pair: str) -> Optional[float]: """ Returns the amount precision of the exchange. - :param pair: + :param pair: Pair to get precision for + :return: precision for amount or None. Must be used in combination with precisionMode """ return self.markets.get(pair, {}).get('precision', {}).get('amount', None) def get_precision_price(self, pair: str) -> Optional[float]: """ Returns the price precision of the exchange. - :param pair: + :param pair: Pair to get precision for + :return: precision for price or None. Must be used in combination with precisionMode """ return self.markets.get(pair, {}).get('precision', {}).get('price', None) From e8187974277f0cdc92be13233ea65397097f2039 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Aug 2022 20:29:05 +0200 Subject: [PATCH 15/26] Minor fix in amount_to_precision logic --- freqtrade/exchange/exchange.py | 4 +++- tests/rpc/test_rpc.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7629b1e01..f4a06d929 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2855,8 +2855,10 @@ def amount_to_precision(amount: float, amount_precision: Optional[float], :return: truncated amount """ if amount_precision is not None and precisionMode is not None: + precision = int(amount_precision) if precisionMode != TICK_SIZE else amount_precision + # precision must be an int for non-ticksize inputs. amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, - precision=amount_precision, + precision=precision, counting_mode=precisionMode, )) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1a2428fe7..64cad6daf 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -761,7 +761,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: # and trade amount is updated rpc._rpc_force_exit('3') assert cancel_order_mock.call_count == 1 - assert trade.amount == filled_amount + assert pytest.approx(trade.amount) == filled_amount mocker.patch( 'freqtrade.exchange.Exchange.fetch_order', From e4b7bcaeabf029b934da2b9f5ea580d5f4552ee0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 08:01:07 +0200 Subject: [PATCH 16/26] Fix some tests --- tests/test_freqtradebot.py | 2 +- tests/test_integration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index fb5fd38d8..8d005826e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2172,7 +2172,7 @@ def test_handle_trade( assert trade.close_rate == (2.0 if is_short else 2.2) assert pytest.approx(trade.close_profit) == close_profit - assert trade.calc_profit(trade.close_rate) == 5.685 + assert pytest.approx(trade.calc_profit(trade.close_rate)) == 5.685 assert trade.close_date is not None assert trade.exit_reason == 'sell_signal1' diff --git a/tests/test_integration.py b/tests/test_integration.py index b970e1c89..0b2639879 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -263,7 +263,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert trade.orders[0].amount == 30 assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid'] - assert trade.amount == trade.orders[0].amount + trade.orders[1].amount + assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount assert trade.nr_of_successful_buys == 2 assert trade.nr_of_successful_entries == 2 From 1dd56e35d502ebf31a25584eed226b5f040f70a8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 08:21:02 +0200 Subject: [PATCH 17/26] Ensure comparisions align when closing a trade --- freqtrade/persistence/trade_model.py | 7 ++++--- tests/test_integration.py | 10 ++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 436919bb1..25b290bfb 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -875,13 +875,14 @@ class LocalTrade(): self.realized_profit = close_profit_abs self.close_profit_abs = profit - if current_amount > ZERO: + current_amount_tr = amount_to_precision(float(current_amount), + self.amount_precision, self.precision_mode) + if current_amount_tr > 0.0: # Trade is still open # Leverage not updated, as we don't allow changing leverage through DCA at the moment. self.open_rate = price_to_precision(float(current_stake / current_amount), self.price_precision, self.precision_mode) - self.amount = amount_to_precision(float(current_amount), - self.amount_precision, self.precision_mode) + self.amount = current_amount_tr self.stake_amount = float(current_stake) / (self.leverage or 1.0) self.fee_open_cost = self.fee_open * float(current_stake) self.recalc_open_trade_value() diff --git a/tests/test_integration.py b/tests/test_integration.py index 0b2639879..dd3488f81 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -220,8 +220,6 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - amount_to_precision=lambda s, x, y: y, - price_to_precision=lambda s, x, y: y, ) patch_get_signal(freqtrade) @@ -249,7 +247,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert len(trade.orders) == 2 for o in trade.orders: assert o.status == "closed" - assert trade.stake_amount == 120 + assert pytest.approx(trade.stake_amount) == 120 # Open-rate averaged between 2.0 and 2.0 * 0.995 assert trade.open_rate < 2.0 @@ -259,9 +257,9 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 - assert trade.stake_amount == 120 + assert pytest.approx(trade.stake_amount) == 120 assert trade.orders[0].amount == 30 - assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid'] + assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid'] assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount assert trade.nr_of_successful_buys == 2 @@ -274,7 +272,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert trade.is_open is False assert trade.orders[0].amount == 30 assert trade.orders[0].side == 'buy' - assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid'] + assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid'] # Sold everything assert trade.orders[-1].side == 'sell' assert trade.orders[2].amount == trade.amount From a5b438e41e5a8a882f4ff9ff6acfe2c7b0db372b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 08:43:03 +0200 Subject: [PATCH 18/26] Run price_to_precision for dry-run orders --- freqtrade/exchange/exchange.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 18598e92d..d50007dab 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1019,7 +1019,8 @@ class Exchange: time_in_force: str = 'gtc', ) -> Dict: if self._config['dry_run']: - dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage) + dry_order = self.create_dry_run_order( + pair, ordertype, side, amount, self.price_to_precision(pair, rate), leverage) return dry_order params = self._get_params(side, ordertype, leverage, reduceOnly, time_in_force) From 2fb7a3091d99a400e53c7ccb4dd38de5702aca7a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 09:32:31 +0200 Subject: [PATCH 19/26] Improve backfill of precisions --- freqtrade/freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0895e5cb0..1b133f88b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -292,6 +292,8 @@ class FreqtradeBot(LoggingMixin): trades = Trade.get_trades([Trade.precision_mode.is_(None)]) for trade in trades: + if trade.exchange != self.exchange.id: + continue trade.precision_mode = self.exchange.precisionMode trade.amount_precision = self.exchange.get_precision_amount(trade.pair) trade.price_precision = self.exchange.get_precision_price(trade.pair) From a73e4f8e41f7bd6fd97cb788f297828533cd95c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 09:49:53 +0200 Subject: [PATCH 20/26] Truncate amount before comparing for closure --- freqtrade/persistence/trade_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 25b290bfb..66b0b2ddb 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -621,7 +621,8 @@ class LocalTrade(): else: logger.warning( f'Got different open_order_id {self.open_order_id} != {order.order_id}') - if isclose(order.safe_amount_after_fee, self.amount, abs_tol=MATH_CLOSE_PREC): + amount_tr = amount_to_precision(self.amount, self.amount_precision, self.precision_mode) + if isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC): self.close(order.safe_price) else: self.recalc_trade_from_orders() From 15a1c59a9130ebf63b508e0c22557506b342e2f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 10:15:10 +0200 Subject: [PATCH 21/26] Backtesting should cache precisionMode --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c8b6ee97d..c0d1a8a2f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -131,6 +131,7 @@ class Backtesting: self.fee = config['fee'] else: self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0]) + self.precision_mode = self.exchange.precisionMode self.timerange = TimeRange.parse_timerange( None if self.config.get('timerange') is None else str(self.config.get('timerange'))) @@ -851,7 +852,7 @@ class Backtesting: # interest_rate=interest_rate, amount_precision=self.exchange.get_precision_amount(pair), price_precision=self.exchange.get_precision_price(pair), - precision_mode=self.exchange.precisionMode, + precision_mode=self.self.precision_mode, orders=[], ) From fa89368c027c5233e2d9368ee5dadcd6f374945d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 10:59:43 +0200 Subject: [PATCH 22/26] Add test for precision backpopulation --- freqtrade/optimize/backtesting.py | 2 +- tests/conftest_trades_usdt.py | 16 ++++++++-------- tests/plugins/test_pairlist.py | 6 +++--- tests/rpc/test_rpc.py | 2 +- tests/test_freqtradebot.py | 31 ++++++++++++++++++++++++++++--- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c0d1a8a2f..6528481d5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -852,7 +852,7 @@ class Backtesting: # interest_rate=interest_rate, amount_precision=self.exchange.get_precision_amount(pair), price_precision=self.exchange.get_precision_price(pair), - precision_mode=self.self.precision_mode, + precision_mode=self.precision_mode, orders=[], ) diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index 9a89eaca6..d54a416ef 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -81,7 +81,7 @@ def mock_trade_usdt_1(fee, is_short: bool): def mock_order_usdt_2(is_short: bool): return { 'id': f'1235_{direc(is_short)}', - 'symbol': 'ETC/USDT', + 'symbol': 'NEO/USDT', 'status': 'closed', 'side': entry_side(is_short), 'type': 'limit', @@ -95,7 +95,7 @@ def mock_order_usdt_2(is_short: bool): def mock_order_usdt_2_exit(is_short: bool): return { 'id': f'12366_{direc(is_short)}', - 'symbol': 'ETC/USDT', + 'symbol': 'NEO/USDT', 'status': 'closed', 'side': exit_side(is_short), 'type': 'limit', @@ -111,7 +111,7 @@ def mock_trade_usdt_2(fee, is_short: bool): Closed trade... """ trade = Trade( - pair='ETC/USDT', + pair='NEO/USDT', stake_amount=200.0, amount=100.0, amount_requested=100.0, @@ -132,10 +132,10 @@ def mock_trade_usdt_2(fee, is_short: bool): close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), is_short=is_short, ) - o = Order.parse_from_ccxt_object(mock_order_usdt_2(is_short), 'ETC/USDT', entry_side(is_short)) + o = Order.parse_from_ccxt_object(mock_order_usdt_2(is_short), 'NEO/USDT', entry_side(is_short)) trade.orders.append(o) o = Order.parse_from_ccxt_object( - mock_order_usdt_2_exit(is_short), 'ETC/USDT', exit_side(is_short)) + mock_order_usdt_2_exit(is_short), 'NEO/USDT', exit_side(is_short)) trade.orders.append(o) return trade @@ -205,7 +205,7 @@ def mock_trade_usdt_3(fee, is_short: bool): def mock_order_usdt_4(is_short: bool): return { 'id': f'prod_buy_12345_{direc(is_short)}', - 'symbol': 'ETC/USDT', + 'symbol': 'NEO/USDT', 'status': 'open', 'side': entry_side(is_short), 'type': 'limit', @@ -221,7 +221,7 @@ def mock_trade_usdt_4(fee, is_short: bool): Simulate prod entry """ trade = Trade( - pair='ETC/USDT', + pair='NEO/USDT', stake_amount=20.0, amount=10.0, amount_requested=10.01, @@ -236,7 +236,7 @@ def mock_trade_usdt_4(fee, is_short: bool): timeframe=5, is_short=is_short, ) - o = Order.parse_from_ccxt_object(mock_order_usdt_4(is_short), 'ETC/USDT', entry_side(is_short)) + o = Order.parse_from_ccxt_object(mock_order_usdt_4(is_short), 'NEO/USDT', entry_side(is_short)) trade.orders.append(o) return trade diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index d3d08dc8c..5974bee89 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -735,7 +735,7 @@ def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> N with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: create_mock_trades_usdt(fee) pm.refresh_pairlist() - assert pm.whitelist == ['XRP/USDT'] + assert pm.whitelist == ['XRP/USDT', 'NEO/USDT'] assert log_has_re(r'Removing pair .* since .* is below .*', caplog) # Move to "outside" of lookback window, so original sorting is restored. @@ -762,8 +762,8 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: create_mock_trades_usdt(fee) pm.refresh_pairlist() - assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', 'LTC/USDT', - 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', ] + assert pm.whitelist == ['XRP/USDT', 'NEO/USDT', 'ETH/USDT', 'LTC/USDT', + 'TKN/USDT', 'ADA/USDT', 'ETC/USDT', ] # assert log_has_re(r'Removing pair .* since .* is below .*', caplog) # Move to "outside" of lookback window, so original sorting is restored. diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 64cad6daf..e28f6510d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -830,7 +830,7 @@ def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: res = rpc._rpc_performance() assert len(res) == 3 - assert res[0]['pair'] == 'ETC/USDT' + assert res[0]['pair'] == 'NEO/USDT' assert res[0]['count'] == 1 assert res[0]['profit_pct'] == 5.0 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ec52aeedd..0be469b75 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -23,9 +23,9 @@ from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections.iprotection import ProtectionReturn from freqtrade.worker import Worker -from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker, - log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal, - patch_wallet, patch_whitelist) +from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, + get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, + patch_get_signal, patch_wallet, patch_whitelist) from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1, mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, mock_order_4, mock_order_5_stoploss, mock_order_6_sell) @@ -4888,6 +4888,31 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s assert hto_mock.call_args_list[1][0][0]['status'] == 'canceled' +@pytest.mark.usefixtures("init_persistence") +def test_startup_backpopulate_precision(mocker, default_conf_usdt, fee, caplog): + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + create_mock_trades_usdt(fee) + + trades = Trade.get_trades().all() + trades[-1].exchange = 'some_other_exchange' + for trade in trades: + assert trade.price_precision is None + assert trade.amount_precision is None + assert trade.precision_mode is None + + freqtrade.startup_backpopulate_precision() + trades = Trade.get_trades().all() + for trade in trades: + if trade.exchange == 'some_other_exchange': + assert trade.price_precision is None + assert trade.amount_precision is None + assert trade.precision_mode is None + else: + assert trade.price_precision is not None + assert trade.amount_precision is not None + assert trade.precision_mode is not None + + @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("is_short", [False, True]) def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short): From 3b44dc52e1dc8325dbf539468e0df0e5cd0e2ce0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 18:10:48 +0200 Subject: [PATCH 23/26] Minor corrections --- freqtrade/persistence/trade_model.py | 6 +++--- tests/test_persistence.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 66b0b2ddb..3317d0ceb 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1128,9 +1128,9 @@ class Trade(_DECL_BASE, LocalTrade): timeframe = Column(Integer, nullable=True) trading_mode = Column(Enum(TradingMode), nullable=True) - amount_precision = Column(Float) - price_precision = Column(Float) - precision_mode = Column(Integer) + amount_precision = Column(Float, nullable=True) + price_precision = Column(Float, nullable=True) + precision_mode = Column(Integer, nullable=True) # Leverage trading properties leverage = Column(Float, nullable=True, default=1.0) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8a3a18577..f68791b72 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1387,7 +1387,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert log_has("trying trades_bak2", caplog) assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0", caplog) - assert round(trade.open_trade_value, 15) == trade._calc_open_trade_value( + assert pytest.approx(trade.open_trade_value) == trade._calc_open_trade_value( trade.amount, trade.open_rate) assert trade.close_profit_abs is None From 24690c1918428f695b6d45bc20e6c3c7ec1c1128 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Aug 2022 18:16:23 +0200 Subject: [PATCH 24/26] Don't convert open_rate to precision this may cause more problems than it solves. --- freqtrade/persistence/trade_model.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 3317d0ceb..1c96d9b76 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -14,7 +14,7 @@ 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_precision, price_to_precision +from freqtrade.exchange import amount_to_precision from freqtrade.leverage import interest from freqtrade.persistence.base import _DECL_BASE from freqtrade.util import FtPrecise @@ -881,8 +881,7 @@ class LocalTrade(): if current_amount_tr > 0.0: # Trade is still open # Leverage not updated, as we don't allow changing leverage through DCA at the moment. - self.open_rate = price_to_precision(float(current_stake / current_amount), - self.price_precision, self.precision_mode) + self.open_rate = float(current_stake / current_amount) self.amount = current_amount_tr self.stake_amount = float(current_stake) / (self.leverage or 1.0) self.fee_open_cost = self.fee_open * float(current_stake) From 8d182768f94fb0d26225a7218fbcaa63f43bc738 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 17 Aug 2022 09:51:28 +0200 Subject: [PATCH 25/26] stoploss should also use trimmed prices --- freqtrade/persistence/trade_model.py | 10 ++++++---- tests/rpc/test_rpc.py | 22 +++++++++++----------- tests/test_freqtradebot.py | 16 ++++++---------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 1c96d9b76..b954fee20 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -14,7 +14,7 @@ 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_precision +from freqtrade.exchange import amount_to_precision, price_to_precision from freqtrade.leverage import interest from freqtrade.persistence.base import _DECL_BASE from freqtrade.util import FtPrecise @@ -527,9 +527,10 @@ class LocalTrade(): """ Method used internally to set self.stop_loss. """ + stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode) if not self.stop_loss: - self.initial_stop_loss = stop_loss - self.stop_loss = stop_loss + self.initial_stop_loss = stop_loss_norm + self.stop_loss = stop_loss_norm self.stop_loss_pct = -1 * abs(percent) self.stoploss_last_update = datetime.utcnow() @@ -557,7 +558,8 @@ class LocalTrade(): # no stop loss assigned yet if self.initial_stop_loss_pct is None or refresh: self.__set_stop_loss(new_loss, stoploss) - self.initial_stop_loss = new_loss + self.initial_stop_loss = price_to_precision( + new_loss, self.price_precision, self.precision_mode) self.initial_stop_loss_pct = -1 * abs(stoploss) # evaluate if the stop loss needs to be updated diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e28f6510d..7b42bf083 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -96,20 +96,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_pct': -0.41, 'profit_abs': -4.09e-06, 'profit_fiat': ANY, - 'stop_loss_abs': 9.882e-06, + 'stop_loss_abs': 9.89e-06, 'stop_loss_pct': -10.0, 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_abs': 9.89e-06, 'initial_stop_loss_pct': -10.0, 'initial_stop_loss_ratio': -0.1, - 'stoploss_current_dist': -1.1080000000000002e-06, - 'stoploss_current_dist_ratio': -0.10081893, - 'stoploss_current_dist_pct': -10.08, - 'stoploss_entry_dist': -0.00010475, - 'stoploss_entry_dist_ratio': -0.10448878, + 'stoploss_current_dist': pytest.approx(-1.0999999e-06), + 'stoploss_current_dist_ratio': -0.10009099, + 'stoploss_current_dist_pct': -10.01, + 'stoploss_entry_dist': -0.00010402, + 'stoploss_entry_dist_ratio': -0.10376381, 'open_order': None, 'realized_profit': 0.0, 'exchange': 'binance', @@ -181,20 +181,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_pct': ANY, 'profit_abs': ANY, 'profit_fiat': ANY, - 'stop_loss_abs': 9.882e-06, + 'stop_loss_abs': 9.89e-06, 'stop_loss_pct': -10.0, 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_abs': 9.89e-06, 'initial_stop_loss_pct': -10.0, 'initial_stop_loss_ratio': -0.1, 'stoploss_current_dist': ANY, 'stoploss_current_dist_ratio': ANY, 'stoploss_current_dist_pct': ANY, - 'stoploss_entry_dist': -0.00010475, - 'stoploss_entry_dist_ratio': -0.10448878, + 'stoploss_entry_dist': -0.00010402, + 'stoploss_entry_dist_ratio': -0.10376381, 'open_order': None, 'exchange': 'binance', 'realized_profit': 0.0, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 0be469b75..c3d738075 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4,7 +4,6 @@ import logging import time from copy import deepcopy -from math import isclose from typing import List from unittest.mock import ANY, MagicMock, PropertyMock, patch @@ -12,7 +11,7 @@ import arrow import pytest from pandas import DataFrame -from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT +from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode, SignalDirection, State) from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, @@ -569,7 +568,7 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim assert trade.open_date is not None assert trade.exchange == 'binance' assert trade.open_rate == ticker_usdt.return_value[ticker_side] - assert isclose(trade.amount, 60 / ticker_usdt.return_value[ticker_side]) + assert pytest.approx(trade.amount) == 60 / ticker_usdt.return_value[ticker_side] assert log_has( f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT ' @@ -1801,7 +1800,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, # stoploss initially at 20% as edge dictated it. assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert isclose(trade.stop_loss, 1.76) + assert pytest.approx(trade.stop_loss) == 1.76 cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() @@ -1818,7 +1817,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, assert freqtrade.handle_stoploss_on_exchange(trade) is False # stoploss should remain the same - assert isclose(trade.stop_loss, 1.76) + assert pytest.approx(trade.stop_loss) == 1.76 # stoploss on exchange should not be canceled cancel_order_mock.assert_not_called() @@ -4524,11 +4523,8 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount changes by fee amount. - assert isclose( - freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj), - amount - (amount * 0.001), - abs_tol=MATH_CLOSE_PREC, - ) + assert pytest.approx(freqtrade.get_real_amount( + trade, limit_buy_order_usdt, order_obj)) == amount - (amount * 0.001) def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker): From b9667f50cfe20377078243acaa211ab7589d4f19 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 17 Aug 2022 14:05:12 +0200 Subject: [PATCH 26/26] Fix random test failure --- tests/test_freqtradebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c3d738075..a7ab6c614 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4143,6 +4143,7 @@ def test_trailing_stop_loss_positive( 'last': enter_price + (-0.06 if is_short else 0.06), }) ) + caplog.clear() # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "