Merge pull request #7216 from freqtrade/precise_calcs

Precise calcs
This commit is contained in:
Matthias
2022-08-17 14:32:02 +02:00
committed by GitHub
19 changed files with 279 additions and 203 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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", [

View File

@@ -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)

View File

@@ -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.

View File

@@ -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,
@@ -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',
@@ -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

View File

@@ -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}]

View File

@@ -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,
@@ -23,9 +22,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)
@@ -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()
@@ -2172,7 +2171,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'
@@ -4144,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: "
@@ -4524,11 +4524,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):
@@ -4958,6 +4955,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):

View File

@@ -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()
@@ -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,11 +257,11 @@ 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 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
@@ -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

View File

@@ -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 pytest.approx(trade.open_trade_value) == trade._calc_open_trade_value(
trade.amount, trade.open_rate)
assert trade.close_profit_abs is None
orders = trade.orders