Merge branch 'develop' into pr/froggleston/7861

This commit is contained in:
Matthias
2023-04-28 14:59:03 +02:00
171 changed files with 6832 additions and 3114 deletions
+2 -10
View File
@@ -3,7 +3,7 @@ import json
import logging
import re
from copy import deepcopy
from datetime import datetime, timedelta
from datetime import timedelta
from pathlib import Path
from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock
@@ -12,7 +12,6 @@ import arrow
import numpy as np
import pandas as pd
import pytest
from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade.commands import Arguments
@@ -504,7 +503,7 @@ def get_default_conf(testdatadir):
{"method": "StaticPairList"}
],
"telegram": {
"enabled": True,
"enabled": False,
"token": "token",
"chat_id": "0",
"notification_settings": {},
@@ -550,13 +549,6 @@ def get_default_conf_usdt(testdatadir):
return configuration
@pytest.fixture
def update():
_update = Update(0)
_update.message = Message(0, datetime.utcnow(), Chat(0, 0))
return _update
@pytest.fixture
def fee():
return MagicMock(return_value=0.0025)
+1 -1
View File
@@ -98,7 +98,7 @@ def test_load_backtest_data_new_format(testdatadir):
assert bt_data.equals(bt_data3)
with pytest.raises(ValueError, match=r"File .* does not exist\."):
load_backtest_data(str("filename") + "nofile")
load_backtest_data("filename" + "nofile")
with pytest.raises(ValueError, match=r"Unknown dataformat."):
load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN)
+53 -1
View File
@@ -252,7 +252,7 @@ def test_datahandler__check_empty_df(testdatadir, caplog):
assert log_has_re(expected_text, caplog)
@pytest.mark.parametrize('datahandler', ['feather', 'parquet'])
@pytest.mark.parametrize('datahandler', ['parquet'])
def test_datahandler_trades_not_supported(datahandler, testdatadir, ):
dh = get_datahandler(testdatadir, datahandler)
with pytest.raises(NotImplementedError):
@@ -496,6 +496,58 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
assert unlinkmock.call_count == 2
def test_featherdatahandler_trades_load(testdatadir):
dh = get_datahandler(testdatadir, 'feather')
trades = dh.trades_load('XRP/ETH')
assert isinstance(trades, list)
assert trades[0][0] == 1570752011620
assert trades[-1][-1] == 0.1986231
trades1 = dh.trades_load('UNITTEST/NONEXIST')
assert trades1 == []
def test_featherdatahandler_trades_store(testdatadir, tmpdir):
tmpdir1 = Path(tmpdir)
dh = get_datahandler(testdatadir, 'feather')
trades = dh.trades_load('XRP/ETH')
dh1 = get_datahandler(tmpdir1, 'feather')
dh1.trades_store('XRP/NEW', trades)
file = tmpdir1 / 'XRP_NEW-trades.feather'
assert file.is_file()
# Load trades back
trades_new = dh1.trades_load('XRP/NEW')
assert len(trades_new) == len(trades)
assert trades[0][0] == trades_new[0][0]
assert trades[0][1] == trades_new[0][1]
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
assert trades[0][3] == trades_new[0][3]
assert trades[0][4] == trades_new[0][4]
assert trades[0][5] == trades_new[0][5]
assert trades[0][6] == trades_new[0][6]
assert trades[-1][0] == trades_new[-1][0]
assert trades[-1][1] == trades_new[-1][1]
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
assert trades[-1][3] == trades_new[-1][3]
assert trades[-1][4] == trades_new[-1][4]
assert trades[-1][5] == trades_new[-1][5]
assert trades[-1][6] == trades_new[-1][6]
def test_featherdatahandler_trades_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = get_datahandler(testdatadir, 'feather')
assert not dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 0
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
assert dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 1
def test_gethandlerclass():
cl = get_datahandlerclass('json')
assert cl == JsonDataHandler
+1 -1
View File
@@ -409,7 +409,7 @@ def test_init_with_refresh(default_conf, mocker) -> None:
def test_file_dump_json_tofile(testdatadir) -> None:
file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
file = testdatadir / f'test_{uuid.uuid4()}.json'
data = {'bar': 'foo'}
# check the file we will create does not exist
+17 -5
View File
@@ -11,6 +11,19 @@ from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('side,type,time_in_force,expected', [
('buy', 'limit', 'gtc', {'timeInForce': 'GTC'}),
('buy', 'limit', 'IOC', {'timeInForce': 'IOC'}),
('buy', 'market', 'IOC', {}),
('buy', 'limit', 'PO', {'timeInForce': 'PO'}),
('sell', 'limit', 'PO', {'timeInForce': 'PO'}),
('sell', 'market', 'PO', {}),
])
def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected):
exchange = get_patched_exchange(mocker, default_conf, id='binance')
assert exchange._get_params(side, type, 1, False, time_in_force) == expected
@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT])
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
@@ -35,11 +48,11 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte
default_conf['margin_mode'] = MarginMode.ISOLATED
default_conf['trading_mode'] = trademode
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(
pair='ETH/BTC',
amount=1,
@@ -114,11 +127,11 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
order_type = 'stop_loss_limit'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(
pair='ETH/BTC',
amount=1,
@@ -542,7 +555,6 @@ def test__set_leverage_binance(mocker, default_conf):
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=TradingMode.FUTURES
)
+14 -12
View File
@@ -37,7 +37,7 @@ EXCHANGES = {
'stake_currency': 'USDT',
'use_ci_proxy': True,
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
@@ -66,7 +66,7 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures': False,
'sample_order': [{
"symbol": "SOLUSDT",
@@ -91,7 +91,7 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
},
@@ -99,7 +99,7 @@ EXCHANGES = {
'pair': 'XRP/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'sample_order': [
@@ -141,7 +141,7 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
@@ -215,7 +215,7 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': False,
@@ -226,7 +226,7 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
@@ -253,14 +253,14 @@ EXCHANGES = {
'pair': 'ETH/BTC',
'stake_currency': 'BTC',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'futures': False,
},
'bitvavo': {
'pair': 'BTC/EUR',
'stake_currency': 'EUR',
'hasQuoteVolume': True,
'timeframe': '5m',
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
@@ -528,9 +528,11 @@ class TestCCXTExchange():
assert res[1] == timeframe
assert res[2] == candle_type
candles = res[3]
candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * 0.9
candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms
assert len(candles) >= min(candle_count, candle_count1)
factor = 0.9
candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * factor
candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms * factor
assert len(candles) >= min(candle_count, candle_count1), \
f"{len(candles)} < {candle_count} in {timeframe}, Offset: {offset} {factor}"
assert candles[0][0] == since_ms or (since_ms + timeframe_ms)
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
+104 -75
View File
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
import arrow
import ccxt
import pytest
from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE
from pandas import DataFrame
from freqtrade.enums import CandleType, MarginMode, TradingMode
@@ -113,18 +114,21 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
exchange.close()
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
exchange.close()
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
exchange.close()
def test_init(default_conf, mocker, caplog):
@@ -312,35 +316,54 @@ def test_amount_to_precision(amount, precision_mode, precision, expected,):
assert amount_to_precision(amount, precision, precision_mode) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3456),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.346),
(2.9999, 2, 3, 3.000),
(2.9909, 2, 3, 2.991),
# Tests for Tick_size
(2.34559, 4, 0.0001, 2.3456),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.346),
(2.9999, 4, 0.001, 3.000),
(2.9909, 4, 0.001, 2.991),
(2.9909, 4, 0.005, 2.995),
(2.9973, 4, 0.005, 3.0),
(2.9977, 4, 0.005, 3.0),
(234.43, 4, 0.5, 234.5),
(234.53, 4, 0.5, 235.0),
(0.891534, 4, 0.0001, 0.8916),
(64968.89, 4, 0.01, 64968.89),
(0.000000003483, 4, 1e-12, 0.000000003483),
@pytest.mark.parametrize("price,precision_mode,precision,expected,rounding_mode", [
# Tests for DECIMAL_PLACES, ROUND_UP
(2.34559, 2, 4, 2.3456, ROUND_UP),
(2.34559, 2, 5, 2.34559, ROUND_UP),
(2.34559, 2, 3, 2.346, ROUND_UP),
(2.9999, 2, 3, 3.000, ROUND_UP),
(2.9909, 2, 3, 2.991, ROUND_UP),
# Tests for DECIMAL_PLACES, ROUND
(2.345600000000001, DECIMAL_PLACES, 4, 2.3456, ROUND),
(2.345551, DECIMAL_PLACES, 4, 2.3456, ROUND),
(2.49, DECIMAL_PLACES, 0, 2., ROUND),
(2.51, DECIMAL_PLACES, 0, 3., ROUND),
(5.1, DECIMAL_PLACES, -1, 10., ROUND),
(4.9, DECIMAL_PLACES, -1, 0., ROUND),
# Tests for TICK_SIZE, ROUND_UP
(2.34559, TICK_SIZE, 0.0001, 2.3456, ROUND_UP),
(2.34559, TICK_SIZE, 0.00001, 2.34559, ROUND_UP),
(2.34559, TICK_SIZE, 0.001, 2.346, ROUND_UP),
(2.9999, TICK_SIZE, 0.001, 3.000, ROUND_UP),
(2.9909, TICK_SIZE, 0.001, 2.991, ROUND_UP),
(2.9909, TICK_SIZE, 0.005, 2.995, ROUND_UP),
(2.9973, TICK_SIZE, 0.005, 3.0, ROUND_UP),
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND_UP),
(234.43, TICK_SIZE, 0.5, 234.5, ROUND_UP),
(234.53, TICK_SIZE, 0.5, 235.0, ROUND_UP),
(0.891534, TICK_SIZE, 0.0001, 0.8916, ROUND_UP),
(64968.89, TICK_SIZE, 0.01, 64968.89, ROUND_UP),
(0.000000003483, TICK_SIZE, 1e-12, 0.000000003483, ROUND_UP),
# Tests for TICK_SIZE, ROUND
(2.49, TICK_SIZE, 1., 2., ROUND),
(2.51, TICK_SIZE, 1., 3., ROUND),
(2.000000051, TICK_SIZE, 0.0000001, 2.0000001, ROUND),
(2.000000049, TICK_SIZE, 0.0000001, 2., ROUND),
(2.9909, TICK_SIZE, 0.005, 2.990, ROUND),
(2.9973, TICK_SIZE, 0.005, 2.995, ROUND),
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND),
(234.24, TICK_SIZE, 0.5, 234., ROUND),
(234.26, TICK_SIZE, 0.5, 234.5, ROUND),
# Tests for TRUNCATTE
(2.34559, 2, 4, 2.3455, TRUNCATE),
(2.34559, 2, 5, 2.34559, TRUNCATE),
(2.34559, 2, 3, 2.345, TRUNCATE),
(2.9999, 2, 3, 2.999, TRUNCATE),
(2.9909, 2, 3, 2.990, TRUNCATE),
])
def test_price_to_precision(price, precision_mode, precision, expected):
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
assert price_to_precision(price, precision, precision_mode) == expected
def test_price_to_precision(price, precision_mode, precision, expected, rounding_mode):
assert price_to_precision(
price, precision, precision_mode, rounding_mode=rounding_mode) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
@@ -414,7 +437,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
}
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss))
expected_result = 2 * 2 * (1 + 0.05)
assert pytest.approx(result) == expected_result
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
@@ -423,14 +446,14 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
assert result == 20000
# min amount and cost are set (cost is minimal)
# min amount and cost are set (cost is minimal and therefore ignored)
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 2, 'max': None},
'amount': {'min': 2, 'max': None},
}
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss))
expected_result = max(2, 2 * 2) * (1 + 0.05)
assert pytest.approx(result) == expected_result
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
@@ -473,6 +496,9 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
assert result == 1000
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, 12.0)
assert result == 1000 / 12
markets["ETH/BTC"]["contractSize"] = '0.01'
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
@@ -1039,9 +1065,9 @@ def test_validate_ordertypes(default_conf, mocker):
('bybit', 'last', True),
('bybit', 'mark', True),
('bybit', 'index', True),
# ('okx', 'last', True),
# ('okx', 'mark', True),
# ('okx', 'index', True),
('okx', 'last', True),
('okx', 'mark', True),
('okx', 'index', True),
('gate', 'last', True),
('gate', 'mark', True),
('gate', 'index', True),
@@ -1229,9 +1255,10 @@ def test_create_dry_run_order_fees(
("buy", 29.563, True, True),
("sell", 21.563, True, True),
])
@pytest.mark.parametrize("leverage", [1, 2, 5])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog,
exchange_name, order_book_l2_usd, converted):
exchange_name, order_book_l2_usd, converted, leverage):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple(EXMS,
@@ -1245,7 +1272,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
side=side,
amount=1,
rate=price,
leverage=1.0
leverage=leverage,
)
assert order_book_l2_usd.call_count == 1
assert 'id' in order
@@ -1269,6 +1296,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
assert order_book_l2_usd.call_count == (1 if not filled else 0)
assert order_closed['status'] == ('open' if not filled else 'closed')
assert order_closed['filled'] == (0 if not filled else 1)
assert order_closed['cost'] == 1 * order_closed['average']
order_book_l2_usd.reset_mock()
@@ -1291,9 +1319,10 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
("sell", 25.564, 1000, 25.5555), # More than orderbook return
("sell", 27, 10000, 25.65), # max-slippage 5%
])
@pytest.mark.parametrize("leverage", [1, 2, 5])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice,
exchange_name, order_book_l2_usd):
exchange_name, order_book_l2_usd, leverage):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple(EXMS,
@@ -1307,7 +1336,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
side=side,
amount=amount,
rate=rate,
leverage=1.0
leverage=leverage,
)
assert 'id' in order
assert f'dry_run_{side}_' in order["id"]
@@ -1316,6 +1345,8 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
assert order["symbol"] == "LTC/USDT"
assert order['status'] == 'closed'
assert order['filled'] == amount
assert order['amount'] == amount
assert pytest.approx(order['cost']) == amount * order['average']
assert round(order["average"], 4) == round(endprice, 4)
@@ -1436,7 +1467,10 @@ def test_buy_prod(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
if exchange._order_needs_price(order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
@@ -1541,7 +1575,10 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
if exchange._order_needs_price(order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
@@ -1585,7 +1622,10 @@ def test_sell_prod(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
if exchange._order_needs_price(order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
@@ -1679,7 +1719,10 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
if exchange._order_needs_price(order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
@@ -2248,7 +2291,6 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
assert res[pair2].at[0, 'open']
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
ohlcv = [
@@ -2277,7 +2319,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
assert res[3] == ohlcv
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog)
exchange.close()
# exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv",
@@ -2292,15 +2334,17 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
(arrow.utcnow().int_timestamp - 2000) * 1000)
exchange.close()
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical candle \(OHLCV\) data\..*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
(arrow.utcnow().int_timestamp - 2000) * 1000)
exchange.close()
@pytest.mark.asyncio
async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
from freqtrade.exchange.common import _reset_logging_mixin
_reset_logging_mixin()
@@ -2341,9 +2385,9 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
# Expect the "returned exception" message 12 times (4 retries * 3 (loop))
assert num_log_has_re(msg, caplog) == 12
assert num_log_has_re(msg2, caplog) == 9
exchange.close()
@pytest.mark.asyncio
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
""" Test empty exchange result """
ohlcv = []
@@ -2363,6 +2407,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
assert res[2] == CandleType.SPOT
assert res[3] == ohlcv
assert exchange._api_async.fetch_ohlcv.call_count == 1
exchange.close()
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
@@ -2757,7 +2802,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
assert res_ohlcv[9][5] == 2.31452783
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
@@ -2785,8 +2829,8 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
exchange.close()
exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_fetch_trades", "fetch_trades",
pair='ABCD/BTC', since=None)
@@ -2796,15 +2840,16 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
exchange.close()
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical trade data\..*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
exchange.close()
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
@@ -2839,6 +2884,7 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e
pair = 'ETH/USDT:USDT'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
assert res[0][5] == 300
exchange.close()
@pytest.mark.asyncio
@@ -3387,7 +3433,7 @@ def test_merge_ft_has_dict(default_conf, mocker):
ex = Binance(default_conf)
assert ex._ft_has != Exchange._ft_has_default
assert ex.get_option('stoploss_on_exchange')
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC']
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC', 'PO']
assert ex.get_option('trades_pagination') == 'id'
assert ex.get_option('trades_pagination_arg') == 'fromId'
@@ -3868,29 +3914,6 @@ def test_get_stake_amount_considering_leverage(
stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize("exchange_name,trading_mode", [
("binance", TradingMode.FUTURES),
])
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
default_conf['dry_run'] = False
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
exchange_name,
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=trading_mode
)
@pytest.mark.parametrize("margin_mode", [
(MarginMode.CROSS),
(MarginMode.ISOLATED)
@@ -4830,7 +4853,6 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name
)
@pytest.mark.asyncio
@pytest.mark.parametrize('exchange_name', EXCHANGES)
async def test_get_market_leverage_tiers(mocker, default_conf, exchange_name):
default_conf['exchange']['name'] = exchange_name
@@ -5287,7 +5309,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
})
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_contract_size = MagicMock(return_value=contract_size)
@@ -5307,3 +5329,10 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
assert order['cost'] == 100
assert order['filled'] == 100
assert order['remaining'] == 100
def test_price_to_precision_with_default_conf(default_conf, mocker):
conf = copy.deepcopy(default_conf)
patched_ex = get_patched_exchange(mocker, conf)
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
assert prec_price == 1.00000001
-33
View File
@@ -4,42 +4,9 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Gate
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import EXMS, get_patched_exchange
def test_validate_order_types_gate(default_conf, mocker):
default_conf['exchange']['name'] = 'gate'
mocker.patch(f'{EXMS}._init_ccxt')
mocker.patch(f'{EXMS}._load_markets', return_value={})
mocker.patch(f'{EXMS}.validate_pairs')
mocker.patch(f'{EXMS}.validate_timeframes')
mocker.patch(f'{EXMS}.validate_stakecurrency')
mocker.patch(f'{EXMS}.validate_pricing')
mocker.patch(f'{EXMS}.name', 'Gate')
exch = ExchangeResolver.load_exchange('gate', default_conf, True)
assert isinstance(exch, Gate)
default_conf['order_types'] = {
'entry': 'market',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
ExchangeResolver.load_exchange('gate', default_conf, True)
# market-orders supported on futures markets.
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
ex = ExchangeResolver.load_exchange('gate', default_conf, True)
assert ex
@pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_gate(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='gate')
+5 -5
View File
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from freqtrade.exceptions import DependencyException, InvalidOrderException
from tests.conftest import EXMS, get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@@ -27,11 +27,11 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
})
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side=side,
@@ -80,11 +80,11 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
order_type = 'stop-limit'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0)
+3 -3
View File
@@ -29,7 +29,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(
@@ -192,7 +192,7 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
@@ -263,7 +263,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
+5 -5
View File
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from freqtrade.exceptions import DependencyException, InvalidOrderException
from tests.conftest import EXMS, get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@@ -27,11 +27,11 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected
})
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
if order_type == 'limit':
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={
'stoploss': order_type,
@@ -88,11 +88,11 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
order_type = 'market'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
with pytest.raises(OperationalException):
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss': 'limit',
'stoploss_on_exchange_limit_ratio': 1.05},
+119 -3
View File
@@ -1,12 +1,14 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
from unittest.mock import AsyncMock, MagicMock, PropertyMock
import ccxt
import pytest
from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import RetryableOrderError, TemporaryError
from freqtrade.exchange.exchange import timeframe_to_minutes
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
from tests.conftest import EXMS, get_patched_exchange, log_has
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@@ -276,7 +278,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
'fetchLeverageTiers': False,
'fetchMarketLeverageTiers': True,
})
api_mock.fetch_market_leverage_tiers = get_mock_coro(side_effect=[
api_mock.fetch_market_leverage_tiers = AsyncMock(side_effect=[
[
{
'tier': 1,
@@ -339,6 +341,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
}
},
],
TemporaryError("this Failed"),
[
{
'tier': 1,
@@ -476,3 +479,116 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
exchange.load_leverage_tiers()
assert log_has(logmsg, caplog)
def test__set_leverage_okx(mocker, default_conf):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
exchange._lev_prep('BTC/USDT:USDT', 3.2, 'buy')
assert api_mock.set_leverage.call_count == 1
# Leverage is rounded to 3.
assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3.2
assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT'
assert api_mock.set_leverage.call_args_list[0][1]['params'] == {
'mgnMode': 'isolated',
'posSide': 'net'}
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"okx",
"_lev_prep",
"set_leverage",
pair="XRP/USDT:USDT",
leverage=5.0,
side='buy'
)
@pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_okx(default_conf, mocker):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock()
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
assert api_mock.fetch_order.call_count == 1
assert api_mock.fetch_order.call_args_list[0][0][0] == '1234'
assert api_mock.fetch_order.call_args_list[0][0][1] == 'ETH/BTC'
assert api_mock.fetch_order.call_args_list[0][1]['params'] == {'stop': True}
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound)
api_mock.fetch_open_orders = MagicMock(return_value=[])
api_mock.fetch_closed_orders = MagicMock(return_value=[])
api_mock.fetch_canceled_orders = MagicMock(creturn_value=[])
with pytest.raises(RetryableOrderError):
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
assert api_mock.fetch_order.call_count == 1
assert api_mock.fetch_open_orders.call_count == 1
assert api_mock.fetch_closed_orders.call_count == 1
assert api_mock.fetch_canceled_orders.call_count == 1
api_mock.fetch_order.reset_mock()
api_mock.fetch_open_orders.reset_mock()
api_mock.fetch_closed_orders.reset_mock()
api_mock.fetch_canceled_orders.reset_mock()
api_mock.fetch_closed_orders = MagicMock(return_value=[
{
'id': '1234',
'status': 'closed',
'info': {'ordId': '123455'}
}
])
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value={'id': '123455'}))
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
assert api_mock.fetch_order.call_count == 1
assert api_mock.fetch_open_orders.call_count == 1
assert api_mock.fetch_closed_orders.call_count == 1
assert api_mock.fetch_canceled_orders.call_count == 0
assert resp['id'] == '1234'
assert resp['id_stop'] == '123455'
assert resp['type'] == 'stoploss'
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={'id': '123455'}))
api_mock.fetch_order.reset_mock()
api_mock.fetch_open_orders.reset_mock()
api_mock.fetch_closed_orders.reset_mock()
api_mock.fetch_canceled_orders.reset_mock()
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
assert api_mock.fetch_order.call_count == 0
assert api_mock.fetch_open_orders.call_count == 0
assert api_mock.fetch_closed_orders.call_count == 0
assert api_mock.fetch_canceled_orders.call_count == 0
assert dro_mock.call_count == 1
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='okx')
order = {
'type': 'stoploss',
'price': 1500,
'stopLossPrice': 1500,
}
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
+19
View File
@@ -1,5 +1,6 @@
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict
from unittest.mock import MagicMock
import pytest
@@ -85,6 +86,22 @@ def make_rl_config(conf):
return conf
def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
return {
"learning_rate": 3e-4,
"trainer_kwargs": {
"max_iters": 1,
"batch_size": 64,
"max_n_eval_batches": 1,
},
"model_kwargs": {
"hidden_dim": 32,
"dropout_percent": 0.2,
"n_layer": 1,
}
}
def get_patched_data_kitchen(mocker, freqaiconf):
dk = FreqaiDataKitchen(freqaiconf)
return dk
@@ -119,6 +136,7 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
freqai.dk.pair = "ADA/BTC"
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
@@ -152,6 +170,7 @@ def make_data_dictionary(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
freqai.dk.pair = "ADA/BTC"
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
+6 -1
View File
@@ -19,6 +19,7 @@ def test_update_historic_data(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -41,6 +42,7 @@ def test_load_all_pairs_histories(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -60,6 +62,7 @@ def test_get_base_and_corr_dataframes(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
@@ -87,6 +90,7 @@ def test_use_strategy_to_populate_indicators(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
@@ -103,8 +107,9 @@ def test_get_timerange_from_live_historic_predictions(mocker, freqai_conf):
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
freqai = strategy.freqai
freqai.live = True
freqai.live = False
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = False
timerange = TimeRange.parse_timerange("20180126-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
+1
View File
@@ -180,6 +180,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
+49 -14
View File
@@ -15,7 +15,8 @@ from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config
from tests.freqai.conftest import (get_patched_freqai_strategy, make_rl_config,
mock_pytorch_mlp_model_training_parameters)
def is_py11() -> bool:
@@ -34,13 +35,14 @@ def is_mac() -> bool:
def can_run_model(model: str) -> None:
if (is_arm() or is_py11()) and "Catboost" in model:
pytest.skip("CatBoost is not supported on ARM")
pytest.skip("CatBoost is not supported on ARM.")
if is_mac() and not is_arm() and 'Reinforcement' in model:
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
if is_pytorch_model and is_mac() and not is_arm():
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
if is_py11() and 'Reinforcement' in model:
pytest.skip("Reinforcement learning currently not available on python 3.11.")
if is_pytorch_model and is_py11():
pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.")
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
@@ -48,11 +50,12 @@ def can_run_model(model: str) -> None:
('XGBoostRegressor', False, True, False, True, False, 10),
('XGBoostRFRegressor', False, False, False, True, False, 0),
('CatboostRegressor', False, False, False, True, True, 0),
('PyTorchMLPRegressor', False, False, False, True, False, 0),
('ReinforcementLearner', False, True, False, True, False, 0),
('ReinforcementLearner_multiproc', False, False, False, True, False, 0),
('ReinforcementLearner_test_3ac', False, False, False, False, False, 0),
('ReinforcementLearner_test_3ac', False, False, False, True, False, 0),
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0)
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0),
])
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
dbscan, float32, can_short, shuffle, buffer):
@@ -79,6 +82,11 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
if 'PyTorchMLPRegressor' in model:
model_save_ext = 'zip'
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
@@ -87,6 +95,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai.live = True
freqai.can_short = can_short
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
freqai.dk.set_paths('ADA/BTC', 10000)
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -122,8 +131,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat")
])
def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat):
if (is_arm() or is_py11()) and 'Catboost' in model:
pytest.skip("CatBoost is not supported on ARM")
can_run_model(model)
freqai_conf.update({"timerange": "20180110-20180130"})
freqai_conf.update({"strategy": strat})
@@ -135,6 +143,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -162,10 +171,10 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
'CatboostClassifier',
'XGBoostClassifier',
'XGBoostRFClassifier',
'PyTorchMLPClassifier',
])
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
if (is_arm() or is_py11()) and model == 'CatboostClassifier':
pytest.skip("CatBoost is not supported on ARM")
can_run_model(model)
freqai_conf.update({"freqaimodel": model})
freqai_conf.update({"strategy": "freqai_test_classifier"})
@@ -178,6 +187,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -190,7 +200,20 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
strategy, freqai.dk, data_load_timerange)
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists()
if 'PyTorchMLPClassifier':
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
if freqai.dd.model_type == 'joblib':
model_file_extension = ".joblib"
elif freqai.dd.model_type == "pytorch":
model_file_extension = ".zip"
else:
raise Exception(f"Unsupported model type: {freqai.dd.model_type},"
f" can't assign model_file_extension")
assert Path(freqai.dk.data_path /
f"{freqai.dk.model_filename}_model{model_file_extension}").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists()
@@ -204,10 +227,12 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
("LightGBMRegressor", 2, "freqai_test_strat"),
("XGBoostRegressor", 2, "freqai_test_strat"),
("CatboostRegressor", 2, "freqai_test_strat"),
("PyTorchMLPRegressor", 2, "freqai_test_strat"),
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
("XGBoostClassifier", 2, "freqai_test_classifier"),
("LightGBMClassifier", 2, "freqai_test_classifier"),
("CatboostClassifier", 2, "freqai_test_classifier")
("CatboostClassifier", 2, "freqai_test_classifier"),
("PyTorchMLPClassifier", 2, "freqai_test_classifier")
],
)
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
@@ -228,6 +253,10 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
if 'test_4ac' in model:
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
if 'PyTorchMLP' in model:
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"indicator_periods_candles": [2]})
@@ -371,6 +400,9 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
df = strategy.set_freqai_targets(df.copy(), metadata={"pair": "LTC/BTC"})
df = freqai.dk.remove_special_chars_from_feature_names(df)
freqai.dk.get_unique_classes_from_labels(df)
freqai.dk.pair = "ADA/BTC"
freqai.dk.full_df = df.fillna(0)
freqai.dk.full_df
@@ -394,6 +426,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -425,10 +458,12 @@ def test_plot_feature_importance(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
freqai.dd.pair_dict = MagicMock()
freqai.dd.pair_dict = {"ADA/BTC": {"model_filename": "fake_name",
"trained_timestamp": 1, "data_path": "", "extras": {}}}
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
+4 -2
View File
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.data.history import get_timerange
from freqtrade.enums import ExitType
from freqtrade.enums import ExitType, TradingMode
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence.trade_model import LocalTrade
from tests.conftest import EXMS, patch_exchange
@@ -925,12 +925,14 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0)
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
# TODO: Should we initialize this properly??
backtesting._can_short = True
backtesting.trading_mode = TradingMode.MARGIN
backtesting._set_strategy(backtesting.strategylist[0])
backtesting._can_short = True
backtesting.required_startup = 0
backtesting.strategy.advise_entry = lambda a, m: frame
backtesting.strategy.advise_exit = lambda a, m: frame
+4 -2
View File
@@ -344,7 +344,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
assert backtesting.progress.progress == 0
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
def test_backtesting_start(default_conf, mocker, caplog) -> None:
def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
@@ -367,6 +367,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.bot_loop_start = MagicMock()
backtesting.strategy.bot_start = MagicMock()
backtesting.start()
# check the logs, that will contain the backtest result
exists = [
@@ -376,7 +377,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
for line in exists:
assert log_has(line, caplog)
assert backtesting.strategy.dp._pairlists is not None
assert backtesting.strategy.bot_loop_start.call_count == 1
assert backtesting.strategy.bot_start.call_count == 1
assert backtesting.strategy.bot_loop_start.call_count == 0
assert sbs.call_count == 1
assert sbc.call_count == 1
@@ -10,7 +10,7 @@ from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.history import get_timerange
from freqtrade.enums import ExitType
from freqtrade.enums import ExitType, TradingMode
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import EXMS, patch_exchange
@@ -108,9 +108,10 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
default_conf.update({
"stake_amount": 100.0,
"dry_run_wallet": 1000.0,
"strategy": "StrategyTestV3"
"strategy": "StrategyTestV3",
})
backtesting = Backtesting(default_conf)
backtesting.trading_mode = TradingMode.FUTURES
backtesting._can_short = True
backtesting._set_strategy(backtesting.strategylist[0])
pair = 'XRP/USDT'
+6 -3
View File
@@ -872,7 +872,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_loop_started is True
assert hyperopt.backtesting.strategy.bot_started is True
assert hyperopt.backtesting.strategy.bot_loop_started is False
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
@@ -922,7 +923,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir,
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_loop_started is True
assert hyperopt.backtesting.strategy.bot_started is True
assert hyperopt.backtesting.strategy.bot_loop_started is False
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
@@ -959,7 +961,8 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_loop_started is True
assert hyperopt.backtesting.strategy.bot_loop_started is False
assert hyperopt.backtesting.strategy.bot_started is True
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
+7 -4
View File
@@ -9,7 +9,7 @@ import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats)
@@ -236,7 +236,7 @@ def test_store_backtest_candles(testdatadir, mocker):
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
dump_mock.reset_mock()
# mock file exporting
@@ -245,7 +245,7 @@ def test_store_backtest_candles(testdatadir, mocker):
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
# result will be testdatadir / testresult-<timestamp>_signals.pkl
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
dump_mock.reset_mock()
@@ -466,11 +466,14 @@ def test_generate_periodic_breakdown_stats(testdatadir):
def test__get_resample_from_period():
assert _get_resample_from_period('day') == '1d'
assert _get_resample_from_period('week') == '1w'
assert _get_resample_from_period('week') == '1W-MON'
assert _get_resample_from_period('month') == '1M'
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
_get_resample_from_period('noooo')
for period in BACKTEST_BREAKDOWNS:
assert isinstance(_get_resample_from_period(period), str)
def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
filename = testdatadir / "backtest_results/backtest-result.json"
+69
View File
@@ -0,0 +1,69 @@
from datetime import datetime, timedelta, timezone
import pytest
from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
from tests.conftest import create_mock_trades_usdt
@pytest.mark.usefixtures("init_persistence")
def test_key_value_store(time_machine):
start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
time_machine.move_to(start, tick=False)
KeyValueStore.store_value("test", "testStringValue")
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
KeyValueStore.store_value("test_float", 22.51)
KeyValueStore.store_value("test_int", 15)
assert KeyValueStore.get_value("test") == "testStringValue"
assert KeyValueStore.get_value("test") == "testStringValue"
assert KeyValueStore.get_string_value("test") == "testStringValue"
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
assert KeyValueStore.get_string_value("test_dt") is None
assert KeyValueStore.get_float_value("test_dt") is None
assert KeyValueStore.get_int_value("test_dt") is None
assert KeyValueStore.get_value("test_float") == 22.51
assert KeyValueStore.get_float_value("test_float") == 22.51
assert KeyValueStore.get_value("test_int") == 15
assert KeyValueStore.get_int_value("test_int") == 15
assert KeyValueStore.get_datetime_value("test_int") is None
time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
assert KeyValueStore.get_value("test_dt") == start
# Test update works
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
KeyValueStore.store_value("test_float", 23.51)
assert KeyValueStore.get_value("test_float") == 23.51
# test deleting
KeyValueStore.delete_value("test_float")
assert KeyValueStore.get_value("test_float") is None
# Delete same value again (should not fail)
KeyValueStore.delete_value("test_float")
with pytest.raises(ValueError, match=r"Unknown value type"):
KeyValueStore.store_value("test_float", {'some': 'dict'})
@pytest.mark.usefixtures("init_persistence")
def test_set_startup_time(fee, time_machine):
create_mock_trades_usdt(fee)
start = datetime.now(timezone.utc)
time_machine.move_to(start, tick=False)
set_startup_time()
assert KeyValueStore.get_value("startup_time") == start
initial_time = KeyValueStore.get_value("bot_start_time")
assert initial_time <= start
# Simulate bot restart
new_start = start + timedelta(days=5)
time_machine.move_to(new_start, tick=False)
set_startup_time()
assert KeyValueStore.get_value("startup_time") == new_start
assert KeyValueStore.get_value("bot_start_time") == initial_time
+14
View File
@@ -1,15 +1,18 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
from importlib import import_module
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from sqlalchemy import create_engine, select, text
from sqlalchemy.schema import CreateTable
from freqtrade.constants import DEFAULT_DB_PROD_URL
from freqtrade.enums import TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade, init_db
from freqtrade.persistence.base import ModelBase
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
from freqtrade.persistence.models import PairLock
from tests.conftest import log_has
@@ -411,3 +414,14 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
assert len(pairlocks) == 1
pairlocks[0].pair == 'ETH/BTC'
pairlocks[0].side == '*'
@pytest.mark.parametrize('dialect', [
'sqlite', 'postgresql', 'mysql', 'oracle', 'mssql',
])
def test_create_table_compiles(dialect):
dialect_mod = import_module(f"sqlalchemy.dialects.{dialect}")
for table in ModelBase.metadata.tables.values():
create_sql = str(CreateTable(table).compile(dialect=dialect_mod.dialect()))
assert 'CREATE TABLE' in create_sql
+177 -125
View File
@@ -6,7 +6,7 @@ import arrow
import pytest
from sqlalchemy import select
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
@@ -1330,71 +1330,78 @@ def test_to_json(fee):
open_rate=0.123,
exchange='binance',
enter_tag=None,
open_order_id='dry_run_buy_12345'
open_order_id='dry_run_buy_12345',
precision_mode=1,
amount_precision=8.0,
price_precision=7.0,
)
result = trade.to_json()
assert isinstance(result, dict)
assert result == {'trade_id': None,
'pair': 'ADA/USDT',
'base_currency': 'ADA',
'quote_currency': 'USDT',
'is_open': None,
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
'open_order_id': 'dry_run_buy_12345',
'close_date': None,
'close_timestamp': None,
'open_rate': 0.123,
'open_rate_requested': None,
'open_trade_value': 15.1668225,
'fee_close': 0.0025,
'fee_close_cost': None,
'fee_close_currency': None,
'fee_open': 0.0025,
'fee_open_cost': None,
'fee_open_currency': None,
'close_rate': None,
'close_rate_requested': None,
'amount': 123.0,
'amount_requested': 123.0,
'stake_amount': 0.001,
'max_stake_amount': None,
'trade_duration': None,
'trade_duration_s': None,
'realized_profit': 0.0,
'realized_profit_ratio': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'exit_reason': None,
'exit_order_status': None,
'stop_loss_abs': None,
'stop_loss_ratio': None,
'stop_loss_pct': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
'initial_stop_loss_pct': None,
'initial_stop_loss_ratio': None,
'min_rate': None,
'max_rate': None,
'strategy': None,
'enter_tag': None,
'timeframe': None,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
'trading_mode': None,
'funding_fees': None,
'orders': [],
}
assert result == {
'trade_id': None,
'pair': 'ADA/USDT',
'base_currency': 'ADA',
'quote_currency': 'USDT',
'is_open': None,
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
'open_order_id': 'dry_run_buy_12345',
'close_date': None,
'close_timestamp': None,
'open_rate': 0.123,
'open_rate_requested': None,
'open_trade_value': 15.1668225,
'fee_close': 0.0025,
'fee_close_cost': None,
'fee_close_currency': None,
'fee_open': 0.0025,
'fee_open_cost': None,
'fee_open_currency': None,
'close_rate': None,
'close_rate_requested': None,
'amount': 123.0,
'amount_requested': 123.0,
'stake_amount': 0.001,
'max_stake_amount': None,
'trade_duration': None,
'trade_duration_s': None,
'realized_profit': 0.0,
'realized_profit_ratio': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'exit_reason': None,
'exit_order_status': None,
'stop_loss_abs': None,
'stop_loss_ratio': None,
'stop_loss_pct': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
'initial_stop_loss_pct': None,
'initial_stop_loss_ratio': None,
'min_rate': None,
'max_rate': None,
'strategy': None,
'enter_tag': None,
'timeframe': None,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
'trading_mode': None,
'funding_fees': None,
'amount_precision': 8.0,
'price_precision': 7.0,
'precision_mode': 1,
'orders': [],
}
# Simulate dry_run entries
trade = Trade(
@@ -1410,70 +1417,77 @@ def test_to_json(fee):
close_rate=0.125,
enter_tag='buys_signal_001',
exchange='binance',
precision_mode=2,
amount_precision=7.0,
price_precision=8.0,
)
result = trade.to_json()
assert isinstance(result, dict)
assert result == {'trade_id': None,
'pair': 'XRP/BTC',
'base_currency': 'XRP',
'quote_currency': 'BTC',
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
'close_timestamp': int(trade.close_date.timestamp() * 1000),
'open_rate': 0.123,
'close_rate': 0.125,
'amount': 100.0,
'amount_requested': 101.0,
'stake_amount': 0.001,
'max_stake_amount': None,
'trade_duration': 60,
'trade_duration_s': 3600,
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
'initial_stop_loss_pct': None,
'initial_stop_loss_ratio': None,
'realized_profit': 0.0,
'realized_profit_ratio': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'close_rate_requested': None,
'fee_close': 0.0025,
'fee_close_cost': None,
'fee_close_currency': None,
'fee_open': 0.0025,
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': None,
'max_rate': None,
'min_rate': None,
'open_order_id': None,
'open_rate_requested': None,
'open_trade_value': 12.33075,
'exit_reason': None,
'exit_order_status': None,
'strategy': None,
'enter_tag': 'buys_signal_001',
'timeframe': None,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
'trading_mode': None,
'funding_fees': None,
'orders': [],
}
assert result == {
'trade_id': None,
'pair': 'XRP/BTC',
'base_currency': 'XRP',
'quote_currency': 'BTC',
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
'close_timestamp': int(trade.close_date.timestamp() * 1000),
'open_rate': 0.123,
'close_rate': 0.125,
'amount': 100.0,
'amount_requested': 101.0,
'stake_amount': 0.001,
'max_stake_amount': None,
'trade_duration': 60,
'trade_duration_s': 3600,
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
'initial_stop_loss_pct': None,
'initial_stop_loss_ratio': None,
'realized_profit': 0.0,
'realized_profit_ratio': None,
'close_profit': None,
'close_profit_pct': None,
'close_profit_abs': None,
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'close_rate_requested': None,
'fee_close': 0.0025,
'fee_close_cost': None,
'fee_close_currency': None,
'fee_open': 0.0025,
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': None,
'max_rate': None,
'min_rate': None,
'open_order_id': None,
'open_rate_requested': None,
'open_trade_value': 12.33075,
'exit_reason': None,
'exit_order_status': None,
'strategy': None,
'enter_tag': 'buys_signal_001',
'timeframe': None,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
'trading_mode': None,
'funding_fees': None,
'amount_precision': 7.0,
'price_precision': 8.0,
'precision_mode': 2,
'orders': [],
}
def test_stoploss_reinitialization(default_conf, fee):
@@ -2023,6 +2037,7 @@ def test_Trade_object_idem():
'get_mix_tag_performance',
'get_trading_volume',
'from_json',
'validate_string_len',
)
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
'total_profit')
@@ -2041,6 +2056,31 @@ def test_Trade_object_idem():
assert item in trade
@pytest.mark.usefixtures("init_persistence")
def test_trade_truncates_string_fields():
trade = Trade(
pair='ADA/USDT',
stake_amount=20.0,
amount=30.0,
open_rate=2.0,
open_date=datetime.utcnow() - timedelta(minutes=20),
fee_open=0.001,
fee_close=0.001,
exchange='binance',
leverage=1.0,
trading_mode='futures',
enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2,
exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2,
)
Trade.session.add(trade)
Trade.commit()
trade1 = Trade.session.scalars(select(Trade)).first()
assert trade1.enter_tag == 'a' * CUSTOM_TAG_MAX_LENGTH
assert trade1.exit_reason == 'b' * CUSTOM_TAG_MAX_LENGTH
def test_recalc_trade_from_orders(fee):
o1_amount = 100
@@ -2441,7 +2481,7 @@ def test_select_filled_orders(fee):
@pytest.mark.usefixtures("init_persistence")
def test_order_to_ccxt(limit_buy_order_open):
def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open):
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
order.ft_trade_id = 1
@@ -2455,11 +2495,23 @@ def test_order_to_ccxt(limit_buy_order_open):
del raw_order['fee']
del raw_order['datetime']
del raw_order['info']
assert raw_order['stopPrice'] is None
del raw_order['stopPrice']
assert raw_order.get('stopPrice') is None
raw_order.pop('stopPrice', None)
del limit_buy_order_open['datetime']
assert raw_order == limit_buy_order_open
order1 = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'mocked', 'sell')
order1.ft_order_side = 'stoploss'
order1.stop_price = order1.price * 0.9
order1.ft_trade_id = 1
order1.session.add(order1)
Order.session.commit()
order_resp1 = Order.order_by_id(limit_sell_order_usdt_open['id'])
raw_order1 = order_resp1.to_ccxt_object()
assert raw_order1.get('stopPrice') is not None
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('data', [
+2 -2
View File
@@ -50,8 +50,8 @@ def test_trade_fromjson():
"stop_loss_ratio": -0.216,
"stop_loss_pct": -21.6,
"stoploss_order_id": null,
"stoploss_last_update": null,
"stoploss_last_update_timestamp": null,
"stoploss_last_update": "2022-10-18 09:13:42",
"stoploss_last_update_timestamp": 1666077222000,
"initial_stop_loss_abs": 0.1981,
"initial_stop_loss_ratio": -0.216,
"initial_stop_loss_pct": -21.6,
+20 -12
View File
@@ -88,6 +88,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
'amount_precision': 8.0,
'price_precision': 8.0,
'precision_mode': 2,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
@@ -125,17 +128,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'profit_pct': 0.0,
'profit_abs': 0.0,
'total_profit_abs': 0.0,
'stop_loss_abs': 0.0,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_current_dist': -1.099e-05,
'stoploss_current_dist_ratio': -1.0,
'stoploss_current_dist_pct': pytest.approx(-100.0),
'stoploss_entry_dist': -0.0010025,
'stoploss_entry_dist_ratio': -1.0,
'initial_stop_loss_abs': 0.0,
'initial_stop_loss_pct': None,
'initial_stop_loss_ratio': None,
'open_order': '(limit buy rem=91.07468123)',
})
response_unfilled['orders'][0].update({
@@ -554,51 +546,67 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'bot_owned': 9.9, # available stake - reducing by reserved amount
'est_stake': 10.0, # In futures mode, "free" is used here.
'est_stake_bot': 9.9,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': True,
},
{
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'bot_owned': 0,
'est_stake': 0.30794,
'est_stake_bot': 0,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': False,
},
{
'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'bot_owned': 0,
'est_stake': 0.0011562404610161968,
'est_stake_bot': 0,
'used': 5.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': False,
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
'est_stake_bot': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
'is_bot_managed': True,
}
]
assert pytest.approx(result['total_bot']) == 29.9
assert pytest.approx(result['total']) == 30.309096
assert result['starting_capital'] == 10
# Very high starting capital ratio, because the futures position really has the wrong unit.
# TODO: improve this test (see comment above)
assert result['starting_capital_ratio'] == pytest.approx(1.98999999)
def test_rpc_start(mocker, default_conf) -> None:
+22 -18
View File
@@ -1,6 +1,7 @@
"""
Unit test file for rpc/api_server.py
"""
import asyncio
import logging
import time
from datetime import datetime, timedelta, timezone
@@ -282,7 +283,7 @@ def test_api__init__(default_conf, mocker):
"username": "TestUser",
"password": "testPass",
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
@@ -299,10 +300,6 @@ def test_api_UvicornServer(mocker):
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert thread_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
assert thread_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run_in_thread()
@@ -318,10 +315,6 @@ def test_api_UvicornServer_run(mocker):
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert serve_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
assert serve_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run()
@@ -331,13 +324,10 @@ def test_api_UvicornServer_run(mocker):
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
get_mock_coro(None))
asyncio.set_event_loop(asyncio.new_event_loop())
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert serve_mock.call_count == 0
s.install_signal_handlers()
# Original implementation starts a thread - make sure that's not the case
assert serve_mock.call_count == 0
# Fake started to avoid sleeping forever
s.started = True
s.run()
@@ -351,7 +341,7 @@ def test_api_run(default_conf, mocker, caplog):
"username": "TestUser",
"password": "testPass",
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_inst_mock = MagicMock()
server_inst_mock.run_in_thread = MagicMock()
@@ -429,7 +419,7 @@ def test_api_cleanup(default_conf, mocker, caplog):
"username": "TestUser",
"password": "testPass",
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_mock = MagicMock()
server_mock.cleanup = MagicMock()
@@ -490,13 +480,18 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'free': 12.0,
'balance': 12.0,
'used': 0.0,
'bot_owned': pytest.approx(11.879999),
'est_stake': 12.0,
'est_stake_bot': pytest.approx(11.879999),
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': True,
}
assert response['total'] == 12.159513094
assert response['total_bot'] == pytest.approx(11.879999)
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
assert 'starting_capital_pct' in response
@@ -893,6 +888,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
'trading_volume': expected['trading_volume'],
'bot_start_timestamp': 0,
'bot_start_date': '',
}
@@ -1066,6 +1063,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'liquidation_price': None,
'funding_fees': None,
'trading_mode': ANY,
'amount_precision': None,
'price_precision': None,
'precision_mode': None,
'orders': [ANY],
}
@@ -1271,6 +1271,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
'liquidation_price': None,
'funding_fees': None,
'trading_mode': 'spot',
'amount_precision': None,
'price_precision': None,
'precision_mode': None,
'orders': [],
}
@@ -1407,10 +1410,10 @@ def test_api_pair_candles(botclient, ohlcv_history):
])
def test_api_pair_history(botclient, ohlcv_history):
def test_api_pair_history(botclient, mocker):
ftbot, client = botclient
timeframe = '5m'
lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
@@ -1444,6 +1447,7 @@ def test_api_pair_history(botclient, ohlcv_history):
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
assert lfm.call_count == 1
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
@@ -1873,7 +1877,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
"password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN
}})
mocker.patch('freqtrade.rpc.telegram.Updater')
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api')
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
+7 -3
View File
@@ -28,6 +28,7 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
@@ -52,6 +53,7 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
@@ -85,7 +87,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', side_effect=ValueError())
default_conf['telegram']['enabled'] = True
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -99,6 +101,7 @@ def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
default_conf['telegram']['enabled'] = True
default_conf['telegram']['allow_custom_messages'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
@@ -115,9 +118,9 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -166,7 +169,8 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
caplog)
def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
File diff suppressed because it is too large Load Diff
+15 -10
View File
@@ -17,6 +17,10 @@ def get_webhook_dict() -> dict:
"enabled": True,
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
"webhookentry": {
# Intentionally broken, as "entry" should have priority.
"value1": "Buying {pair55555}",
},
"entry": {
"value1": "Buying {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}",
@@ -89,15 +93,15 @@ def test_send_msg_webhook(default_conf, mocker):
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
default_conf["webhook"]["entry"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
default_conf["webhook"]["entry"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
default_conf["webhook"]["entry"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
default_conf["webhook"]["entry"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
default_conf["webhook"]["entry"]["value5"].format(**msg))
# Test short
msg_mock.reset_mock()
@@ -116,15 +120,15 @@ def test_send_msg_webhook(default_conf, mocker):
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
default_conf["webhook"]["entry"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
default_conf["webhook"]["entry"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
default_conf["webhook"]["entry"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
default_conf["webhook"]["entry"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
default_conf["webhook"]["entry"]["value5"].format(**msg))
# Test buy cancel
msg_mock.reset_mock()
@@ -328,6 +332,7 @@ def test_send_msg_webhook(default_conf, mocker):
def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
del default_conf["webhook"]["entry"]
del default_conf["webhook"]["webhookentry"]
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
@@ -82,7 +82,7 @@ class freqai_test_classifier(IStrategy):
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
dataframe["close"], 'up', 'down')
@@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3):
return prot
bot_loop_started = False
bot_started = False
def bot_loop_start(self):
self.bot_loop_started = True
@@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3):
"""
Parameters can also be defined here ...
"""
self.bot_started = True
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
def informative_pairs(self):
+9 -5
View File
@@ -9,6 +9,7 @@ import pytest
from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
@@ -529,13 +530,13 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
assert res[0].exit_reason == 'hello world'
caplog.clear()
strategy.custom_exit = MagicMock(return_value='h' * 100)
strategy.custom_exit = MagicMock(return_value='h' * CUSTOM_TAG_MAX_LENGTH * 2)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_flag is True
assert res[0].exit_reason == 'h' * 64
assert res[0].exit_reason == 'h' * (CUSTOM_TAG_MAX_LENGTH)
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
@@ -986,7 +987,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
}
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
return_value=expected_result)
PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.stoploss == -0.05
@@ -1005,11 +1007,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
return_value=expected_result)
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
StrategyResolver.load_strategy(default_conf)
mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
MagicMock(side_effect=ValueError()))
StrategyResolver.load_strategy(default_conf)
assert log_has("Invalid parameter file format.", caplog)
+1 -1
View File
@@ -69,7 +69,7 @@ def test_load_strategy(default_conf, dataframe_1m):
def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
filepath = Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py'
encoded_string = urlsafe_b64encode(filepath.read_bytes()).decode("utf-8")
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
default_conf.update({'strategy': f'SampleStrategy:{encoded_string}'})
strategy = StrategyResolver.load_strategy(default_conf)
assert 'rsi' in strategy.advise_indicators(dataframe_1m, {'pair': 'ETH/BTC'})
+5 -4
View File
@@ -23,7 +23,8 @@ from freqtrade.configuration.load_config import (load_config_file, load_file, lo
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
from freqtrade.loggers import (FTBufferingHandler, FTStdErrStreamHandler, _set_loggers,
setup_logging, setup_logging_pre)
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
patched_configuration_load_config_file)
@@ -658,7 +659,7 @@ def test_set_loggers_syslog():
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -681,7 +682,7 @@ def test_set_loggers_Filehandler(tmpdir):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -706,7 +707,7 @@ def test_set_loggers_journald(mocker):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
# reset handlers to not break pytest
logger.handlers = orig_handlers
+333 -51
View File
@@ -356,7 +356,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
(0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0.042, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
@@ -1060,9 +1060,19 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order,
@pytest.mark.parametrize("is_short", [False, True])
def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
EXMS,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
get_fee=fee,
)
order = limit_order[entry_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
@@ -1074,8 +1084,10 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# TODO: should not be magicmock
trade = MagicMock()
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.open_order_id = None
trade.stoploss_order_id = None
@@ -1091,7 +1103,8 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
stoploss = MagicMock(return_value={'id': 13434334})
stop_order_dict = {'id': "13434334"}
stoploss = MagicMock(return_value=stop_order_dict)
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
@@ -1116,8 +1129,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# First case: when stoploss is not yet set but the order is open
# should get the stoploss order id immediately
# and should return false as no trade actually happened
# TODO: should not be magicmock
trade = MagicMock()
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
@@ -1129,44 +1143,62 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Second case: when stoploss is set but it is not yet hit
# should do nothing and return false
stop_order_dict.update({'id': "102"})
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "100"
trade.stoploss_order_id = "102"
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='102',
status='open',
)
)
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == "100"
assert trade.stoploss_order_id == "102"
# Third case: when stoploss was set but it was canceled for some reason
# should set a stoploss immediately and return False
caplog.clear()
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "100"
trade.stoploss_order_id = "102"
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock()
amount_before = trade.amount
stop_order_dict.update({'id': "103_1"})
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "13434334"
assert trade.stoploss_order_id == "103_1"
assert trade.amount == amount_before
# Fourth case: when stoploss is set and it is hit
# should unset stoploss_order_id and return true
# as a trade actually happened
caplog.clear()
freqtrade.enter_positions()
stop_order_dict.update({'id': "104"})
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "100"
trade.stoploss_order_id = "104"
trade.orders.append(Order(
ft_order_side='stoploss',
order_id='100',
order_id='104',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
@@ -1175,7 +1207,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert trade
stoploss_order_hit = MagicMock(return_value={
'id': "100",
'id': "104",
'status': 'closed',
'type': 'stop_loss_limit',
'price': 3,
@@ -1197,7 +1229,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Fifth case: fetch_order returns InvalidOrder
# It should try to add stoploss order
trade.stoploss_order_id = 100
stop_order_dict.update({'id': "105"})
trade.stoploss_order_id = "105"
stoploss.reset_mock()
mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
@@ -1217,21 +1250,36 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Seventh case: emergency exit triggered
# Trailing stop should not act anymore
stoploss_order_cancelled = MagicMock(side_effect=[{
'id': "100",
'id': "107",
'status': 'canceled',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'amount': enter_order['amount'],
'filled': 0,
'remaining': enter_order['amount'],
'info': {'stopPrice': 22},
}])
trade.stoploss_order_id = 100
trade.stoploss_order_id = "107"
trade.is_open = True
trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime
trade.stop_loss = 24
trade.exit_reason = None
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='107',
status='open',
)
)
freqtrade.config['trailing_stop'] = True
stoploss = MagicMock(side_effect=InvalidOrderException())
Trade.commit()
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled)
@@ -1242,6 +1290,137 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_partial(
mocker, default_conf_usdt, fee, is_short, limit_order) -> None:
stop_order_dict = {'id': "101", "status": "open"}
stoploss = MagicMock(return_value=stop_order_dict)
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
EXMS,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
enter_order,
exit_order,
]),
get_fee=fee,
create_stoploss=stoploss
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = None
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "101"
assert trade.amount == 30
stop_order_dict.update({'id': "102"})
# Stoploss on exchange is cancelled on exchange, but filled partially.
# Must update trade amount to guarantee successful exit.
stoploss_order_hit = MagicMock(return_value={
'id': "101",
'status': 'canceled',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'filled': trade.amount / 2,
'remaining': trade.amount / 2,
'amount': enter_order['amount'],
})
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# Stoploss filled partially ...
assert trade.amount == 15
assert trade.stoploss_order_id == "102"
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_partial_cancel_here(
mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None:
stop_order_dict = {'id': "101", "status": "open"}
default_conf_usdt['trailing_stop'] = True
stoploss = MagicMock(return_value=stop_order_dict)
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
EXMS,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
enter_order,
exit_order,
]),
get_fee=fee,
create_stoploss=stoploss
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = None
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "101"
assert trade.amount == 30
stop_order_dict.update({'id': "102"})
# Stoploss on exchange is open.
# Freqtrade cancels the stop - but cancel returns a partial filled order.
stoploss_order_hit = MagicMock(return_value={
'id': "101",
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'filled': 0,
'remaining': trade.amount,
'amount': enter_order['amount'],
})
stoploss_order_cancel = MagicMock(return_value={
'id': "101",
'status': 'canceled',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'filled': trade.amount / 2,
'remaining': trade.amount / 2,
'amount': enter_order['amount'],
})
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-10).datetime
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# Canceled Stoploss filled partially ...
assert log_has_re('Cancelling current stoploss on exchange.*', caplog)
assert trade.stoploss_order_id == "102"
assert trade.amount == 15
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
@@ -1273,10 +1452,21 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
assert trade.is_short == is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
trade.stoploss_order_id = "100"
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='100',
status='open',
)
)
assert trade
assert freqtrade.handle_stoploss_on_exchange(trade) is False
@@ -1395,7 +1585,7 @@ def test_handle_stoploss_on_exchange_trailing(
# When trailing stoploss is set
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
stoploss = MagicMock(return_value={'id': 13434334})
stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
patch_RPCManager(mocker)
mocker.patch.multiple(
EXMS,
@@ -1440,11 +1630,21 @@ def test_handle_stoploss_on_exchange_trailing(
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-20).datetime
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='100',
)
)
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': hang_price,
@@ -1471,7 +1671,7 @@ def test_handle_stoploss_on_exchange_trailing(
)
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
@@ -1483,13 +1683,14 @@ def test_handle_stoploss_on_exchange_trailing(
assert freqtrade.handle_trade(trade) is False
assert trade.stop_loss == stop_price[1]
trade.stoploss_order_id = '100'
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(amt),
pair='ETH/USDT',
@@ -1519,7 +1720,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
patch_exchange(mocker)
mocker.patch.multiple(
@@ -1601,7 +1802,7 @@ def test_stoploss_on_exchange_price_rounding(
EXMS,
get_fee=fee,
)
price_mock = MagicMock(side_effect=lambda p, s: int(s))
price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s))
stoploss_mock = MagicMock(return_value={'id': '13434334'})
adjust_mock = MagicMock(return_value=False)
mocker.patch.multiple(
@@ -1628,7 +1829,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
patch_RPCManager(mocker)
mocker.patch.multiple(
EXMS,
@@ -1673,11 +1874,21 @@ def test_handle_stoploss_on_exchange_custom_stop(
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='100',
)
)
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
@@ -1703,9 +1914,10 @@ def test_handle_stoploss_on_exchange_custom_stop(
)
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
trade.stoploss_order_id = '100'
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
@@ -1722,7 +1934,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
# Long uses modified ask - offset, short modified bid + offset
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(trade.amount),
@@ -1751,7 +1963,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
exit_order = limit_order['sell']
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
@@ -1800,11 +2012,21 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
trade = Trade.session.scalars(select(Trade)).first()
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
trade.stoploss_last_update = arrow.utcnow()
trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().datetime
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='100',
)
)
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
@@ -1851,7 +2073,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
# stoploss should be set to 1% as trailing is on
assert trade.stop_loss == 4.4 * 0.99
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
cancel_order_mock.assert_called_once_with('100', 'NEO/BTC')
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(11.41438356),
pair='NEO/BTC',
@@ -1885,6 +2107,7 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@@ -1893,12 +2116,33 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
mocker.patch(f'{EXMS}.fetch_order', return_value=limit_order[entry_side(is_short)])
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
# TODO: should not be magicmock
trade = MagicMock()
trade.is_short = is_short
trade.open_order_id = '123'
trade.open_fee = 0.001
order_id = '123'
trade = Trade(
open_order_id=order_id,
pair='ETH/USDT',
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
open_date=arrow.utcnow().datetime,
stake_amount=0.01,
amount=11,
exchange="binance",
is_short=is_short,
leverage=1,
)
trade.orders.append(Order(
ft_order_side=entry_side(is_short),
price=0.01,
ft_pair=trade.pair,
ft_amount=trade.amount,
ft_price=trade.open_rate,
order_id=order_id,
))
Trade.session.add(trade)
Trade.commit()
trades = [trade]
freqtrade.wallets.update()
n = freqtrade.exit_positions(trades)
assert n == 0
# Test amount not modified by fee-logic
@@ -1911,17 +2155,40 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
assert gra.call_count == 0
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order = limit_order[entry_side(is_short)]
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
# TODO: should not be magicmock
trade = MagicMock()
trade.is_short = is_short
order_id = '123'
trade = Trade(
open_order_id=order_id,
pair='ETH/USDT',
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
open_date=arrow.utcnow().datetime,
stake_amount=0.01,
amount=11,
exchange="binance",
is_short=is_short,
leverage=1,
)
trade.orders.append(Order(
ft_order_side=entry_side(is_short),
price=0.01,
ft_pair=trade.pair,
ft_amount=trade.amount,
ft_price=trade.open_rate,
order_id=order_id,
))
trade.open_order_id = None
trade.pair = 'ETH/USDT'
Trade.session.add(trade)
Trade.commit()
freqtrade.wallets.update()
trades = [trade]
# Test raise of DependencyException exception
@@ -2733,6 +3000,9 @@ def test_manage_open_orders_exit_usercustom(
assert rpc_mock.call_count == 2
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
trade = Trade.session.scalars(select(Trade)).first()
# cancelling didn't succeed - order-id remains open.
assert trade.open_order_id is not None
# 2nd canceled trade - Fail execute exit
caplog.clear()
@@ -3243,6 +3513,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
# TODO: should not be magicmock
trade = MagicMock()
trade.open_order_id = '125'
reason = CANCEL_REASON['TIMEOUT']
order = {'remaining': 1,
'id': '125',
@@ -3250,6 +3521,10 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
'status': "open"}
assert not freqtrade.handle_cancel_exit(trade, order, reason)
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
# assert trade.open_order_id == '125'
@pytest.mark.parametrize("is_short, open_rate, amt", [
(False, 2.0, 30.0),
@@ -3326,6 +3601,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'base_currency': 'ETH',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY,
@@ -3389,6 +3665,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'profit_amount': -5.65990099 if is_short else -0.00075,
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
@@ -3474,6 +3751,7 @@ def test_execute_trade_exit_custom_exit_price(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': 'foo',
'exit_reason': 'foo',
@@ -3547,6 +3825,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'base_currency': 'ETH',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY,
@@ -3588,7 +3867,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
freqtrade.execute_trade_exit(trade=trade, limit=1234,
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
assert create_order_mock.call_count == 2
assert log_has('Could not cancel stoploss order abcd', caplog)
assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog)
@pytest.mark.parametrize("is_short", [False, True])
@@ -3600,10 +3879,12 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
patch_exchange(mocker)
stoploss = MagicMock(return_value={
'id': 123,
'status': 'open',
'info': {
'foo': 'bar'
}
})
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee')
cancel_order = MagicMock(return_value=True)
mocker.patch.multiple(
@@ -3701,12 +3982,12 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
"lastTradeTimestamp": None,
"symbol": "BTC/USDT",
"type": "stop_loss_limit",
"side": "sell",
"side": "buy" if is_short else "sell",
"price": 1.08801,
"amount": 90.99181074,
"cost": 99.0000000032274,
"amount": trade.amount,
"cost": 1.08801 * trade.amount,
"average": 1.08801,
"filled": 90.99181074,
"filled": trade.amount,
"remaining": 0.0,
"status": "closed",
"fee": None,
@@ -3811,6 +4092,7 @@ def test_execute_trade_exit_market_order(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
+18 -16
View File
@@ -35,7 +35,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
"type": "stop_loss_limit",
"side": "sell",
"price": 1.08801,
"amount": 90.99181074,
"amount": 91.07468123,
"cost": 0.0,
"average": 0.0,
"filled": 0.0,
@@ -49,8 +49,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
stoploss_order_mock = MagicMock(
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
[],
@@ -93,13 +94,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
wallets_mock.reset_mock()
trades = Trade.session.scalars(select(Trade)).all()
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
for trade in trades:
stoploss_order_closed['id'] = '3'
oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss')
# Make sure stoploss-order is open and trade is bought
for idx, trade in enumerate(trades):
stop_order = stop_orders[idx]
stop_order['id'] = f"stop{idx}"
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
trade.orders.append(oobj)
trade.stoploss_order_id = '3'
trade.stoploss_order_id = f"stop{idx}"
trade.open_order_id = None
n = freqtrade.exit_positions(trades)
@@ -386,12 +388,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.open_order_id is not None
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 1.96
assert trade.stop_loss_pct is None
assert trade.stop_loss == 0.0
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
assert trade.leverage == leverage
assert trade.stake_amount == 60
assert trade.initial_stop_loss == 0.0
assert trade.initial_stop_loss_pct is None
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
@@ -407,11 +409,11 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.open_order_id is not None
# Open rate is not adjusted yet
assert trade.open_rate == 1.96
assert trade.stop_loss_pct is None
assert trade.stop_loss == 0.0
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert trade.stake_amount == 60
assert trade.initial_stop_loss == 0.0
assert trade.initial_stop_loss_pct is None
assert trade.initial_stop_loss_pct == -0.1
# Fill order
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
@@ -424,7 +426,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.stake_amount) == 60
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == 1.99 * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
# 2nd order - not filling
+2
View File
@@ -10,6 +10,8 @@ from freqtrade.exceptions import OperationalException
def test_parse_timerange_incorrect():
timerange = TimeRange.parse_timerange('')
assert timerange == TimeRange(None, None, 0, 0)
timerange = TimeRange.parse_timerange('20100522-')
assert TimeRange('date', None, 1274486400, 0) == timerange
assert timerange.timerange_str == '20100522-'
Binary file not shown.