Merge remote-tracking branch 'upstream/develop' into feature/fetch-public-trades

This commit is contained in:
Joe Schr
2024-02-01 11:46:38 +01:00
145 changed files with 2773 additions and 1641 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ def test_validate_is_int():
assert not validate_is_int('-ee')
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken'])
@pytest.mark.parametrize('exchange', ['bybit', 'binance', 'kraken'])
def test_start_new_config(mocker, caplog, exchange):
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
+61 -60
View File
@@ -32,7 +32,7 @@ from tests.conftest_trades import MOCK_TRADE_COUNT
def test_setup_utils_configuration():
args = [
'list-exchanges', '--config', 'config_examples/config_bittrex.example.json',
'list-exchanges', '--config', 'tests/testdata/testconfigs/main_test_config.json',
]
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
@@ -49,7 +49,7 @@ def test_start_trading_fail(mocker, caplog):
exitmock = mocker.patch("freqtrade.worker.Worker.exit", MagicMock())
args = [
'trade',
'-c', 'config_examples/config_bittrex.example.json'
'-c', 'tests/testdata/testconfigs/main_test_config.json'
]
start_trading(get_args(args))
assert exitmock.call_count == 1
@@ -68,7 +68,7 @@ def test_start_webserver(mocker, caplog):
args = [
'webserver',
'-c', 'config_examples/config_bittrex.example.json'
'-c', 'tests/testdata/testconfigs/main_test_config.json'
]
start_webserver(get_args(args))
assert api_server_mock.call_count == 1
@@ -84,7 +84,7 @@ def test_list_exchanges(capsys):
captured = capsys.readouterr()
assert re.match(r"Exchanges available for Freqtrade.*", captured.out)
assert re.search(r".*binance.*", captured.out)
assert re.search(r".*bittrex.*", captured.out)
assert re.search(r".*bybit.*", captured.out)
# Test with --one-column
args = [
@@ -95,7 +95,7 @@ def test_list_exchanges(capsys):
start_list_exchanges(get_args(args))
captured = capsys.readouterr()
assert re.search(r"^binance$", captured.out, re.MULTILINE)
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
assert re.search(r"^bybit$", captured.out, re.MULTILINE)
# Test with --all
args = [
@@ -107,7 +107,7 @@ def test_list_exchanges(capsys):
captured = capsys.readouterr()
assert re.match(r"All exchanges supported by the ccxt library.*", captured.out)
assert re.search(r".*binance.*", captured.out)
assert re.search(r".*bittrex.*", captured.out)
assert re.search(r".*bingx.*", captured.out)
assert re.search(r".*bitmex.*", captured.out)
# Test with --one-column --all
@@ -120,7 +120,7 @@ def test_list_exchanges(capsys):
start_list_exchanges(get_args(args))
captured = capsys.readouterr()
assert re.search(r"^binance$", captured.out, re.MULTILINE)
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
assert re.search(r"^bingx$", captured.out, re.MULTILINE)
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
@@ -133,7 +133,7 @@ def test_list_timeframes(mocker, capsys):
'1h': 'hour',
'1d': 'day',
}
patch_exchange(mocker, api_mock=api_mock, id='bittrex')
patch_exchange(mocker, api_mock=api_mock, id='bybit')
args = [
"list-timeframes",
]
@@ -143,25 +143,25 @@ def test_list_timeframes(mocker, capsys):
match=r"This command requires a configured exchange.*"):
start_list_timeframes(pargs)
# Test with --config config_examples/config_bittrex.example.json
# Test with --config tests/testdata/testconfigs/main_test_config.json
args = [
"list-timeframes",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
]
start_list_timeframes(get_args(args))
captured = capsys.readouterr()
assert re.match("Timeframes available for the exchange `Bittrex`: "
assert re.match("Timeframes available for the exchange `Bybit`: "
"1m, 5m, 30m, 1h, 1d",
captured.out)
# Test with --exchange bittrex
# Test with --exchange bybit
args = [
"list-timeframes",
"--exchange", "bittrex",
"--exchange", "bybit",
]
start_list_timeframes(get_args(args))
captured = capsys.readouterr()
assert re.match("Timeframes available for the exchange `Bittrex`: "
assert re.match("Timeframes available for the exchange `Bybit`: "
"1m, 5m, 30m, 1h, 1d",
captured.out)
@@ -190,7 +190,7 @@ def test_list_timeframes(mocker, capsys):
# Test with --one-column
args = [
"list-timeframes",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--one-column",
]
start_list_timeframes(get_args(args))
@@ -217,7 +217,7 @@ def test_list_timeframes(mocker, capsys):
def test_list_markets(mocker, markets_static, capsys):
api_mock = MagicMock()
patch_exchange(mocker, api_mock=api_mock, id='bittrex', mock_markets=markets_static)
patch_exchange(mocker, api_mock=api_mock, id='binance', mock_markets=markets_static)
# Test with no --config
args = [
@@ -229,15 +229,15 @@ def test_list_markets(mocker, markets_static, capsys):
match=r"This command requires a configured exchange.*"):
start_list_markets(pargs, False)
# Test with --config config_examples/config_bittrex.example.json
# Test with --config tests/testdata/testconfigs/main_test_config.json
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 12 active markets: "
assert ("Exchange Binance has 12 active markets: "
"ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, "
"LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out)
@@ -255,16 +255,16 @@ def test_list_markets(mocker, markets_static, capsys):
assert re.match("\nExchange Binance has 12 active markets:\n",
captured.out)
patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static)
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static)
# Test with --all: all markets
args = [
"list-markets", "--all",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 14 markets: "
assert ("Exchange Binance has 14 markets: "
"ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, "
"LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out)
@@ -272,24 +272,24 @@ def test_list_markets(mocker, markets_static, capsys):
# Test list-pairs subcommand: active pairs
args = [
"list-pairs",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-list",
]
start_list_markets(get_args(args), True)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 9 active pairs: "
assert ("Exchange Binance has 9 active pairs: "
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n"
in captured.out)
# Test list-pairs subcommand with --all: all pairs
args = [
"list-pairs", "--all",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-list",
]
start_list_markets(get_args(args), True)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 11 pairs: "
assert ("Exchange Binance has 11 pairs: "
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, "
"TKN/BTC, XRP/BTC.\n"
in captured.out)
@@ -297,133 +297,133 @@ def test_list_markets(mocker, markets_static, capsys):
# active markets, base=ETH, LTC
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "ETH", "LTC",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: "
assert ("Exchange Binance has 7 active markets with ETH, LTC as base currencies: "
"ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
in captured.out)
# active markets, base=LTC
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 4 active markets with LTC as base currency: "
assert ("Exchange Binance has 4 active markets with LTC as base currency: "
"LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
in captured.out)
# active markets, quote=USDT, USD
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--quote", "USDT", "USD",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: "
assert ("Exchange Binance has 5 active markets with USDT, USD as quote currencies: "
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n"
in captured.out)
# active markets, quote=USDT
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--quote", "USDT",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: "
assert ("Exchange Binance has 4 active markets with USDT as quote currency: "
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n"
in captured.out)
# active markets, base=LTC, quote=USDT
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC", "--quote", "USDT",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 1 active market with LTC as base currency and "
assert ("Exchange Binance has 1 active market with LTC as base currency and "
"with USDT as quote currency: XLTCUSDT.\n"
in captured.out)
# active pairs, base=LTC, quote=USDT
args = [
"list-pairs",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC", "--quote", "USD",
"--print-list",
]
start_list_markets(get_args(args), True)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 1 active pair with LTC as base currency and "
assert ("Exchange Binance has 1 active pair with LTC as base currency and "
"with USD as quote currency: LTC/USD.\n"
in captured.out)
# active markets, base=LTC, quote=USDT, NONEXISTENT
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC", "--quote", "USDT", "NONEXISTENT",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 1 active market with LTC as base currency and "
assert ("Exchange Binance has 1 active market with LTC as base currency and "
"with USDT, NONEXISTENT as quote currencies: XLTCUSDT.\n"
in captured.out)
# active markets, base=LTC, quote=NONEXISTENT
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC", "--quote", "NONEXISTENT",
"--print-list",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 0 active markets with LTC as base currency and "
assert ("Exchange Binance has 0 active markets with LTC as base currency and "
"with NONEXISTENT as quote currency.\n"
in captured.out)
# Test tabular output
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 12 active markets:\n"
assert ("Exchange Binance has 12 active markets:\n"
in captured.out)
# Test tabular output, no markets found
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--base", "LTC", "--quote", "NONEXISTENT",
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 0 active markets with LTC as base currency and "
assert ("Exchange Binance has 0 active markets with LTC as base currency and "
"with NONEXISTENT as quote currency.\n"
in captured.out)
# Test --print-json
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-json"
]
start_list_markets(get_args(args), False)
@@ -435,7 +435,7 @@ def test_list_markets(mocker, markets_static, capsys):
# Test --print-csv
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--print-csv"
]
start_list_markets(get_args(args), False)
@@ -447,7 +447,7 @@ def test_list_markets(mocker, markets_static, capsys):
# Test --one-column
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--one-column"
]
start_list_markets(get_args(args), False)
@@ -459,7 +459,7 @@ def test_list_markets(mocker, markets_static, capsys):
# Test --one-column
args = [
"list-markets",
'--config', 'config_examples/config_bittrex.example.json',
'--config', 'tests/testdata/testconfigs/main_test_config.json',
"--one-column"
]
with pytest.raises(OperationalException, match=r"Cannot get markets.*"):
@@ -772,7 +772,7 @@ def test_download_data_all_pairs(mocker, markets):
pargs = get_args(args)
pargs['config'] = None
start_download_data(pargs)
expected = set(['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
expected = set(['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
assert dl_mock.call_count == 1
@@ -788,7 +788,7 @@ def test_download_data_all_pairs(mocker, markets):
pargs = get_args(args)
pargs['config'] = None
start_download_data(pargs)
expected = set(['ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
expected = set(['BTC/USDT', 'ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
@@ -971,7 +971,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
patched_configuration_load_config_file(mocker, default_conf)
args = [
'test-pairlist',
'-c', 'config_examples/config_bittrex.example.json'
'-c', 'tests/testdata/testconfigs/main_test_config.json'
]
start_test_pairlist(get_args(args))
@@ -985,7 +985,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
args = [
'test-pairlist',
'-c', 'config_examples/config_bittrex.example.json',
'-c', 'tests/testdata/testconfigs/main_test_config.json',
'--one-column',
]
start_test_pairlist(get_args(args))
@@ -994,7 +994,7 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
args = [
'test-pairlist',
'-c', 'config_examples/config_bittrex.example.json',
'-c', 'tests/testdata/testconfigs/main_test_config.json',
'--print-json',
]
start_test_pairlist(get_args(args))
@@ -1445,12 +1445,13 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs)
captured = capsys.readouterr()
assert "Found 2 pair / timeframe combinations." in captured.out
assert ("\n| Pair | Timeframe | Type | From | To |\n"
in captured.out)
assert (
"\n| Pair | Timeframe | Type "
"| From | To | Candles |\n") in captured.out
assert "UNITTEST/BTC" not in captured.out
assert (
"\n| XRP/ETH | 1m | spot | 2019-10-11 00:00:00 | 2019-10-13 11:19:00 |\n"
in captured.out)
"\n| XRP/ETH | 1m | spot | "
"2019-10-11 00:00:00 | 2019-10-13 11:19:00 | 2469 |\n") in captured.out
@pytest.mark.usefixtures("init_persistence")
@@ -1508,7 +1509,7 @@ def test_backtesting_show(mocker, testdatadir, capsys):
pargs['config'] = None
start_backtesting_show(pargs)
assert sbr.call_count == 1
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert "Pairs for Strategy" in out
+132 -48
View File
@@ -3,7 +3,7 @@ import json
import logging
import re
from copy import deepcopy
from datetime import timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock
@@ -11,19 +11,18 @@ from unittest.mock import MagicMock, Mock, PropertyMock
import numpy as np
import pandas as pd
import pytest
from xdist.scheduler.loadscope import LoadScopeScheduling
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
from freqtrade.edge import PairInfo
from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import timeframe_to_minutes
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import dt_ts
from freqtrade.util.datetime_helpers import dt_now
from freqtrade.util import dt_now, dt_ts
from freqtrade.worker import Worker
from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3,
mock_trade_4, mock_trade_5, mock_trade_6, short_trade)
@@ -56,6 +55,27 @@ def pytest_configure(config):
setattr(config.option, 'markexpr', 'not longrun')
class FixtureScheduler(LoadScopeScheduling):
# Based on the suggestion in
# https://github.com/pytest-dev/pytest-xdist/issues/18
def _split_scope(self, nodeid):
if 'exchange_online' in nodeid:
try:
# Extract exchange ID from nodeid
exchange_id = nodeid.split('[')[1].split('-')[0].rstrip(']')
return exchange_id
except Exception as e:
print(e)
pass
return nodeid
def pytest_xdist_make_scheduler(config, log):
return FixtureScheduler(config, log)
def log_has(line, logs):
"""Check if line is found on some caplog's message."""
return any(line == message for message in logs.messages)
@@ -85,17 +105,62 @@ def get_args(args):
return Arguments(args).get_parsed_arg()
def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=5):
np.random.seed(42)
if not start_date:
start_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
# Generate random data
end_date = start_date + timedelta(days=days)
_start_timestamp = start_date.timestamp()
_end_timestamp = pd.to_datetime(end_date).timestamp()
random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows)
timestamp = pd.to_datetime(random_timestamps_in_seconds, unit='s')
id = [
f'a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}'
for _ in range(n_rows)
]
side = np.random.choice(['buy', 'sell'], n_rows)
# Initial price and subsequent changes
initial_price = 0.019626
price_changes = np.random.normal(0, initial_price * 0.05, n_rows)
price = np.cumsum(np.concatenate(([initial_price], price_changes)))[:n_rows]
amount = np.random.uniform(0.011, 20, n_rows)
cost = price * amount
# Create DataFrame
df = pd.DataFrame({'timestamp': timestamp, 'id': id, 'type': None, 'side': side,
'price': price, 'amount': amount, 'cost': cost})
df['date'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True)
df = df.sort_values('timestamp').reset_index(drop=True)
assert list(df.columns) == constants.DEFAULT_TRADES_COLUMNS + ['date']
return df
def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05'):
np.random.seed(42)
base = np.random.normal(20, 2, size=size)
if timeframe == '1M':
if timeframe == '1y':
date = pd.date_range(start, periods=size, freq='1YS', tz='UTC')
elif timeframe == '1M':
date = pd.date_range(start, periods=size, freq='1MS', tz='UTC')
elif timeframe == '1w':
elif timeframe == '3M':
date = pd.date_range(start, periods=size, freq='3MS', tz='UTC')
elif timeframe == '1w' or timeframe == '7d':
date = pd.date_range(start, periods=size, freq='1W-MON', tz='UTC')
else:
tf_mins = timeframe_to_minutes(timeframe)
date = pd.date_range(start, periods=size, freq=f'{tf_mins}min', tz='UTC')
if tf_mins >= 1:
date = pd.date_range(start, periods=size, freq=f'{tf_mins}min', tz='UTC')
else:
tf_secs = timeframe_to_seconds(timeframe)
date = pd.date_range(start, periods=size, freq=f'{tf_secs}s', tz='UTC')
df = pd.DataFrame({
'date': date,
'open': base,
@@ -531,6 +596,7 @@ def get_default_conf(testdatadir):
"internals": {},
"export": "none",
"dataformat_ohlcv": "feather",
"runmode": "dry_run",
"candle_type_def": CandleType.SPOT,
}
return configuration
@@ -939,6 +1005,58 @@ def get_markets():
'maintenance_rate': '0.005',
},
},
'BTC/USDT': {
'id': 'USDT-BTC',
'symbol': 'BTC/USDT',
'base': 'BTC',
'quote': 'USDT',
'settle': None,
'baseId': 'BTC',
'quoteId': 'USDT',
'settleId': None,
'type': 'spot',
'spot': True,
'margin': True,
'swap': False,
'future': False,
'option': False,
'active': True,
'contract': None,
'linear': None,
'inverse': None,
'taker': 0.0006,
'maker': 0.0002,
'contractSize': None,
'expiry': None,
'expiryDateTime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': 4,
'price': 4,
},
'limits': {
'leverage': {
'min': 1,
'max': 100,
},
'amount': {
'min': 0.000221,
'max': None,
},
'price': {
'min': 1e-02,
'max': None,
},
'cost': {
'min': None,
'max': None,
},
},
'info': {
'maintenance_rate': '0.005',
},
},
'LTC/USDT': {
'id': 'USDT-LTC',
'symbol': 'LTC/USDT',
@@ -2363,14 +2481,7 @@ def trades_history_df(trades_history):
@pytest.fixture(scope="function")
def fetch_trades_result():
return [{'info': {'a': 126181329,
'p': '0.01962700',
'q': '0.04000000',
'f': 138604155,
'l': 138604155,
'T': 1565798399463,
'm': False,
'M': True},
return [{'info': ['0.01962700', '0.04000000', '1565798399.4631551', 'b', 'm', '', '126181329'],
'timestamp': 1565798399463,
'datetime': '2019-08-14T15:59:59.463Z',
'symbol': 'ETH/BTC',
@@ -2383,14 +2494,7 @@ def fetch_trades_result():
'amount': 0.04,
'cost': 0.00078508,
'fee': None},
{'info': {'a': 126181330,
'p': '0.01962700',
'q': '0.24400000',
'f': 138604156,
'l': 138604156,
'T': 1565798399629,
'm': False,
'M': True},
{'info': ['0.01962700', '0.24400000', '1565798399.6291551', 'b', 'm', '', '126181330'],
'timestamp': 1565798399629,
'datetime': '2019-08-14T15:59:59.629Z',
'symbol': 'ETH/BTC',
@@ -2403,14 +2507,7 @@ def fetch_trades_result():
'amount': 0.244,
'cost': 0.004788987999999999,
'fee': None},
{'info': {'a': 126181331,
'p': '0.01962600',
'q': '0.01100000',
'f': 138604157,
'l': 138604157,
'T': 1565798399752,
'm': True,
'M': True},
{'info': ['0.01962600', '0.01100000', '1565798399.7521551', 's', 'm', '', '126181331'],
'timestamp': 1565798399752,
'datetime': '2019-08-14T15:59:59.752Z',
'symbol': 'ETH/BTC',
@@ -2423,14 +2520,7 @@ def fetch_trades_result():
'amount': 0.011,
'cost': 0.00021588599999999999,
'fee': None},
{'info': {'a': 126181332,
'p': '0.01962600',
'q': '0.01100000',
'f': 138604158,
'l': 138604158,
'T': 1565798399862,
'm': True,
'M': True},
{'info': ['0.01962600', '0.01100000', '1565798399.8621551', 's', 'm', '', '126181332'],
'timestamp': 1565798399862,
'datetime': '2019-08-14T15:59:59.862Z',
'symbol': 'ETH/BTC',
@@ -2443,14 +2533,8 @@ def fetch_trades_result():
'amount': 0.011,
'cost': 0.00021588599999999999,
'fee': None},
{'info': {'a': 126181333,
'p': '0.01952600',
'q': '0.01200000',
'f': 138604158,
'l': 138604158,
'T': 1565798399872,
'm': True,
'M': True},
{'info': ['0.01952600', '0.01200000', '1565798399.8721551', 's', 'm', '', '126181333',
1565798399872512133],
'timestamp': 1565798399872,
'datetime': '2019-08-14T15:59:59.872Z',
'symbol': 'ETH/BTC',
+84 -1
View File
@@ -17,7 +17,8 @@ from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from freqtrade.data.history.idatahandler import IDataHandler
from freqtrade.enums import CandleType
from tests.conftest import generate_test_data, log_has, log_has_re
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from tests.conftest import generate_test_data, generate_trades_history, log_has, log_has_re
from tests.data.test_history import _clean_test_file
@@ -51,6 +52,49 @@ def test_trades_to_ohlcv(trades_history_df, caplog):
assert 'close' in df.columns
assert df.iloc[0, :]['high'] == 0.019627
assert df.iloc[0, :]['low'] == 0.019626
assert df.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:59:00+0000')
df_1h = trades_to_ohlcv(trades_history_df, '1h')
assert len(df_1h) == 1
assert df_1h.iloc[0, :]['high'] == 0.019627
assert df_1h.iloc[0, :]['low'] == 0.019626
assert df_1h.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:00:00+0000')
df_1s = trades_to_ohlcv(trades_history_df, '1s')
assert len(df_1s) == 2
assert df_1s.iloc[0, :]['high'] == 0.019627
assert df_1s.iloc[0, :]['low'] == 0.019627
assert df_1s.iloc[0, :]['date'] == pd.Timestamp('2019-08-14 15:59:49+0000')
assert df_1s.iloc[-1, :]['date'] == pd.Timestamp('2019-08-14 15:59:59+0000')
@pytest.mark.parametrize('timeframe,rows,days,candles,start,end,weekday', [
('1s', 20_000, 5, 19522, '2020-01-01 00:00:05', '2020-01-05 23:59:27', None),
('1m', 20_000, 5, 6745, '2020-01-01 00:00:00', '2020-01-05 23:59:00', None),
('5m', 20_000, 5, 1440, '2020-01-01 00:00:00', '2020-01-05 23:55:00', None),
('15m', 20_000, 5, 480, '2020-01-01 00:00:00', '2020-01-05 23:45:00', None),
('1h', 20_000, 5, 120, '2020-01-01 00:00:00', '2020-01-05 23:00:00', None),
('2h', 20_000, 5, 60, '2020-01-01 00:00:00', '2020-01-05 22:00:00', None),
('4h', 20_000, 5, 30, '2020-01-01 00:00:00', '2020-01-05 20:00:00', None),
('8h', 20_000, 5, 15, '2020-01-01 00:00:00', '2020-01-05 16:00:00', None),
('12h', 20_000, 5, 10, '2020-01-01 00:00:00', '2020-01-05 12:00:00', None),
('1d', 20_000, 5, 5, '2020-01-01 00:00:00', '2020-01-05 00:00:00', 'Sunday'),
('7d', 20_000, 37, 6, '2020-01-06 00:00:00', '2020-02-10 00:00:00', 'Monday'),
('1w', 20_000, 37, 6, '2020-01-06 00:00:00', '2020-02-10 00:00:00', 'Monday'),
('1M', 20_000, 74, 3, '2020-01-01 00:00:00', '2020-03-01 00:00:00', None),
('3M', 20_000, 100, 2, '2020-01-01 00:00:00', '2020-04-01 00:00:00', None),
('1y', 20_000, 1000, 3, '2020-01-01 00:00:00', '2022-01-01 00:00:00', None),
])
def test_trades_to_ohlcv_multi(timeframe, rows, days, candles, start, end, weekday):
trades_history = generate_trades_history(n_rows=rows, days=days)
df = trades_to_ohlcv(trades_history, timeframe)
assert not df.empty
assert len(df) == candles
assert df.iloc[0, :]['date'] == pd.Timestamp(f'{start}+0000')
assert df.iloc[-1, :]['date'] == pd.Timestamp(f'{end}+0000')
if weekday:
# Weekday is only relevant for daily and weekly candles.
assert df.iloc[-1, :]['date'].day_name() == weekday
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
@@ -132,6 +176,45 @@ def test_ohlcv_fill_up_missing_data2(caplog):
f"{len(data)} - after: {len(data2)}.*", caplog)
@pytest.mark.parametrize('timeframe', [
'1s', '1m', '5m', '15m', '1h', '2h', '4h', '8h', '12h', '1d', '7d', '1w', '1M', '3M', '1y'
])
def test_ohlcv_to_dataframe_multi(timeframe):
data = generate_test_data(timeframe, 180)
assert len(data) == 180
df = ohlcv_to_dataframe(data, timeframe, 'UNITTEST/USDT')
assert len(df) == len(data) - 1
df1 = ohlcv_to_dataframe(data, timeframe, 'UNITTEST/USDT', drop_incomplete=False)
assert len(df1) == len(data)
assert data.equals(df1)
data1 = data.copy()
if timeframe in ('1M', '3M', '1y'):
data1.loc[:, 'date'] = data1.loc[:, 'date'] + pd.to_timedelta('1w')
else:
# Shift by half a timeframe
data1.loc[:, 'date'] = data1.loc[:, 'date'] + (pd.to_timedelta(timeframe) / 2)
df2 = ohlcv_to_dataframe(data1, timeframe, 'UNITTEST/USDT')
assert len(df2) == len(data) - 1
tfs = timeframe_to_seconds(timeframe)
tfm = timeframe_to_minutes(timeframe)
if 1 <= tfm < 10000:
# minute based resampling does not work on timeframes >= 1 week
ohlcv_dict = {
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
}
dfs = data1.resample(f"{tfs}s", on='date').agg(ohlcv_dict).reset_index(drop=False)
dfm = data1.resample(f"{tfm}min", on='date').agg(ohlcv_dict).reset_index(drop=False)
assert dfs.equals(dfm)
assert dfs.equals(df1)
def test_ohlcv_to_dataframe_1M():
# Monthly ticks from 2019-09-01 to 2023-07-01
+13 -7
View File
@@ -148,19 +148,25 @@ def test_jsondatahandler_ohlcv_load(testdatadir, caplog):
def test_datahandler_ohlcv_data_min_max(testdatadir):
dh = JsonDataHandler(testdatadir)
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '5m', 'spot')
assert len(min_max) == 2
assert len(min_max) == 3
# Empty pair
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '8m', 'spot')
assert len(min_max) == 2
assert len(min_max) == 3
assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc)
assert min_max[0] == min_max[1]
# Empty pair2
min_max = dh.ohlcv_data_min_max('NOPAIR/XXX', '4m', 'spot')
assert len(min_max) == 2
min_max = dh.ohlcv_data_min_max('NOPAIR/XXX', '41m', 'spot')
assert len(min_max) == 3
assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc)
assert min_max[0] == min_max[1]
# Existing pair ...
min_max = dh.ohlcv_data_min_max('UNITTEST/BTC', '1m', 'spot')
assert len(min_max) == 3
assert min_max[0] == datetime(2017, 11, 4, 23, 2, tzinfo=timezone.utc)
assert min_max[1] == datetime(2017, 11, 14, 22, 59, tzinfo=timezone.utc)
def test_datahandler__check_empty_df(testdatadir, caplog):
dh = JsonDataHandler(testdatadir)
@@ -513,11 +519,11 @@ def test_gethandlerclass():
def test_get_datahandler(testdatadir):
dh = get_datahandler(testdatadir, 'json')
assert type(dh) == JsonDataHandler
assert isinstance(dh, JsonDataHandler)
dh = get_datahandler(testdatadir, 'jsongz')
assert type(dh) == JsonGzDataHandler
assert isinstance(dh, JsonGzDataHandler)
dh1 = get_datahandler(testdatadir, 'jsongz', dh)
assert id(dh1) == id(dh)
dh = get_datahandler(testdatadir, 'hdf5')
assert type(dh) == HDF5DataHandler
assert isinstance(dh, HDF5DataHandler)
+13 -40
View File
@@ -194,7 +194,7 @@ def test_get_producer_df(default_conf):
assert la == empty_la
# non existent timeframe, empty dataframe
datframe, la = dataprovider.get_producer_df(pair, timeframe='1h')
_dataframe, la = dataprovider.get_producer_df(pair, timeframe='1h')
assert dataframe.empty
assert la == empty_la
@@ -508,16 +508,13 @@ def test_dp_get_required_startup(default_conf_usdt):
dp = DataProvider(default_conf_usdt, None)
# No FreqAI config
assert dp.get_required_startup('5m', False) == 0
assert dp.get_required_startup('1h', False) == 0
assert dp.get_required_startup('1d', False) == 0
assert dp.get_required_startup('1d', True) == 0
assert dp.get_required_startup('5m') == 0
assert dp.get_required_startup('1h') == 0
assert dp.get_required_startup('1d') == 0
dp._config['startup_candle_count'] = 20
assert dp.get_required_startup('5m', False) == 20
assert dp.get_required_startup('5m', True) == 20
assert dp.get_required_startup('1h', False) == 20
assert dp.get_required_startup('5m') == 20
assert dp.get_required_startup('1h') == 20
assert dp.get_required_startup('1h') == 20
# With freqAI config
@@ -532,37 +529,19 @@ def test_dp_get_required_startup(default_conf_usdt):
]
}
}
assert dp.get_required_startup('5m', False) == 20
assert dp.get_required_startup('5m', True) == 5780
assert dp.get_required_startup('1h', False) == 20
assert dp.get_required_startup('1h', True) == 500
assert dp.get_required_startup('1d', False) == 20
assert dp.get_required_startup('1d', True) == 40
assert dp.get_required_startup('5m') == 5780
assert dp.get_required_startup('1h') == 500
assert dp.get_required_startup('1d') == 40
# FreqAI kindof ignores startup_candle_count if it's below indicator_periods_candles
dp._config['startup_candle_count'] = 0
assert dp.get_required_startup('5m', False) == 20
assert dp.get_required_startup('5m', True) == 5780
assert dp.get_required_startup('1h', False) == 20
assert dp.get_required_startup('1h', True) == 500
assert dp.get_required_startup('1d', False) == 20
assert dp.get_required_startup('1d', True) == 40
assert dp.get_required_startup('5m') == 5780
assert dp.get_required_startup('1h') == 500
assert dp.get_required_startup('1d') == 40
dp._config['freqai']['feature_parameters']['indicator_periods_candles'][1] = 50
assert dp.get_required_startup('5m', False) == 50
assert dp.get_required_startup('5m', True) == 5810
assert dp.get_required_startup('1h', False) == 50
assert dp.get_required_startup('1h', True) == 530
assert dp.get_required_startup('1d', False) == 50
assert dp.get_required_startup('1d', True) == 70
assert dp.get_required_startup('5m') == 5810
assert dp.get_required_startup('1h') == 530
assert dp.get_required_startup('1d') == 70
# scenario from issue https://github.com/freqtrade/freqtrade/issues/9432
@@ -577,12 +556,6 @@ def test_dp_get_required_startup(default_conf_usdt):
}
}
dp._config['startup_candle_count'] = 40
assert dp.get_required_startup('5m', False) == 40
assert dp.get_required_startup('5m', True) == 51880
assert dp.get_required_startup('1h', False) == 40
assert dp.get_required_startup('1h', True) == 4360
assert dp.get_required_startup('1d', False) == 40
assert dp.get_required_startup('1d', True) == 220
assert dp.get_required_startup('5m') == 51880
assert dp.get_required_startup('1h') == 4360
assert dp.get_required_startup('1d') == 220
+2 -2
View File
@@ -38,7 +38,7 @@ def test_download_data_main_all_pairs(mocker, markets):
"timeframes": ["5m", "1h"]
})
download_data_main(config)
expected = set(['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
expected = set(['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
assert dl_mock.call_count == 1
@@ -50,7 +50,7 @@ def test_download_data_main_all_pairs(mocker, markets):
"include_inactive": True
})
download_data_main(config)
expected = set(['ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
expected = set(['BTC/USDT', 'ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'])
assert set(dl_mock.call_args_list[0][1]['pairs']) == expected
+5 -1
View File
@@ -508,8 +508,9 @@ def test_refresh_backtest_ohlcv_data(
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
mocker.patch.object(Path, "unlink", MagicMock())
default_conf['trading_mode'] = trademode
ex = get_patched_exchange(mocker, default_conf)
ex = get_patched_exchange(mocker, default_conf, id='bybit')
timerange = TimeRange.parse_timerange("20190101-20190102")
refresh_backtest_ohlcv_data(exchange=ex, pairs=["ETH/BTC", "XRP/BTC"],
timeframes=["1m", "5m"], datadir=testdatadir,
@@ -521,6 +522,9 @@ def test_refresh_backtest_ohlcv_data(
assert dl_mock.call_args[1]['timerange'].starttype == 'date'
assert log_has_re(r"Downloading pair ETH/BTC, .* interval 1m\.", caplog)
if trademode == 'futures':
assert log_has_re(r"Downloading pair ETH/BTC, funding_rate, interval 8h\.", caplog)
assert log_has_re(r"Downloading pair ETH/BTC, mark, interval 4h\.", caplog)
def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
+113 -43
View File
@@ -13,7 +13,7 @@ from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
InsufficientFundsError, InvalidOrderException,
OperationalException, PricingError, TemporaryError)
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, market_is_active,
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
timeframe_to_prev_date)
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
calculate_backoff, remove_exchange_credentials)
@@ -55,7 +55,7 @@ get_entry_rate_data = [
('bid', 6, 5, None, 0, 5), # last not available - uses bid
]
get_sell_rate_data = [
get_exit_rate_data = [
('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side
('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side
('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat
@@ -228,10 +228,10 @@ def test_exchange_resolver(default_conf, mocker, caplog):
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
default_conf['exchange']['name'] = 'Bittrex'
default_conf['exchange']['name'] = 'Bybit'
exchange = ExchangeResolver.load_exchange(default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Bittrex)
assert isinstance(exchange, Bybit)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
caplog.clear()
@@ -263,8 +263,8 @@ def test_exchange_resolver(default_conf, mocker, caplog):
def test_validate_order_time_in_force(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
# explicitly test bittrex, exchanges implementing other policies need separate tests
ex = get_patched_exchange(mocker, default_conf, id="bittrex")
# explicitly test bybit, exchanges implementing other policies need separate tests
ex = get_patched_exchange(mocker, default_conf, id="bybit")
tif = {
"buy": "gtc",
"sell": "gtc",
@@ -273,11 +273,14 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
ex.validate_order_time_in_force(tif)
tif2 = {
"buy": "fok",
"sell": "ioc",
"sell": "ioc22",
}
with pytest.raises(OperationalException, match=r"Time in force.*not supported for .*"):
ex.validate_order_time_in_force(tif2)
tif2 = {
"buy": "fok",
"sell": "ioc",
}
# Patch to see if this will pass if the values are in the ft dict
ex._ft_has.update({"order_time_in_force": ["GTC", "FOK", "IOC"]})
ex.validate_order_time_in_force(tif2)
@@ -915,7 +918,6 @@ def test_validate_ordertypes(default_conf, mocker):
mocker.patch(f'{EXMS}.validate_timeframes')
mocker.patch(f'{EXMS}.validate_stakecurrency')
mocker.patch(f'{EXMS}.validate_pricing')
mocker.patch(f'{EXMS}.name', 'Bittrex')
default_conf['order_types'] = {
'entry': 'limit',
@@ -2510,8 +2512,10 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
last, last_ab, expected) -> None:
last, last_ab, expected, time_machine) -> None:
caplog.set_level(logging.DEBUG)
start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc)
time_machine.move_to(start_dt, tick=False)
if last_ab is None:
del default_conf['entry_pricing']['price_last_balance']
else:
@@ -2519,39 +2523,65 @@ def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
default_conf['entry_pricing']['price_side'] = side
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid})
log_msg = "Using cached entry rate for ETH/BTC."
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
assert not log_has(log_msg, caplog)
time_machine.move_to(start_dt + timedelta(minutes=4), tick=False)
# Running a 2nd time without Refresh!
caplog.clear()
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
assert log_has("Using cached entry rate for ETH/BTC.", caplog)
assert log_has(log_msg, caplog)
time_machine.move_to(start_dt + timedelta(minutes=6), tick=False)
# Running a 2nd time - forces refresh due to ttl timeout
caplog.clear()
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected
assert not log_has(log_msg, caplog)
# Running a 2nd time with Refresh on!
caplog.clear()
assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
assert not log_has(log_msg, caplog)
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data)
def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask,
last, last_ab, expected) -> None:
last, last_ab, expected, time_machine) -> None:
caplog.set_level(logging.DEBUG)
start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc)
time_machine.move_to(start_dt, tick=False)
default_conf['exit_pricing']['price_side'] = side
if last_ab is not None:
default_conf['exit_pricing']['price_last_balance'] = last_ab
mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last})
pair = "ETH/BTC"
log_msg = "Using cached exit rate for ETH/BTC."
# Test regular mode
exchange = get_patched_exchange(mocker, default_conf)
rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=True)
assert not log_has("Using cached exit rate for ETH/BTC.", caplog)
assert not log_has(log_msg, caplog)
assert isinstance(rate, float)
assert rate == expected
# Use caching
rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=False)
assert rate == expected
assert log_has("Using cached exit rate for ETH/BTC.", caplog)
caplog.clear()
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
assert log_has(log_msg, caplog)
time_machine.move_to(start_dt + timedelta(minutes=4), tick=False)
# Caching still active - TTL didn't expire
caplog.clear()
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
assert log_has(log_msg, caplog)
time_machine.move_to(start_dt + timedelta(minutes=6), tick=False)
# Caching expired - refresh forced
caplog.clear()
assert exchange.get_rate(pair, side="exit", is_short=False, refresh=False) == expected
assert not log_has(log_msg, caplog)
@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [
@@ -2647,9 +2677,9 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short):
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
@pytest.mark.parametrize("side2", ['bid', 'ask'])
@pytest.mark.parametrize("use_order_book", [True, False])
def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
last, last_ab, expected,
side2, use_order_book, order_book_l2) -> None:
def test_get_rates_testing_entry(mocker, default_conf, caplog, side, ask, bid,
last, last_ab, expected,
side2, use_order_book, order_book_l2) -> None:
caplog.set_level(logging.DEBUG)
if last_ab is None:
del default_conf['entry_pricing']['price_last_balance']
@@ -2683,10 +2713,10 @@ def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
assert api_mock.fetch_ticker.call_count == 1
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data)
@pytest.mark.parametrize("side2", ['bid', 'ask'])
@pytest.mark.parametrize("use_order_book", [True, False])
def test_get_rates_testing_sell(default_conf, mocker, caplog, side, bid, ask,
def test_get_rates_testing_exit(default_conf, mocker, caplog, side, bid, ask,
last, last_ab, expected,
side2, use_order_book, order_book_l2) -> None:
caplog.set_level(logging.DEBUG)
@@ -2766,7 +2796,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
assert res_ohlcv[9][4] == 0.07668
assert res_ohlcv[9][5] == 16.65244264
# Bittrex use-case (real data from Bittrex)
# This OHLCV data is ordered ASC (oldest first, newest last)
ohlcv = [
[1527827700000, 0.07659999, 0.0766, 0.07627, 0.07657998, 1.85216924],
@@ -2815,10 +2844,17 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
pair = 'ETH/BTC'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None)
assert isinstance(res, list)
assert isinstance(res[0], list)
assert isinstance(res[1], list)
if exchange._trades_pagination == 'id':
if exchange_name == 'kraken':
assert pagid == 1565798399872512133
else:
assert pagid == '126181333'
else:
assert pagid == 1565798399872
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
@@ -2827,11 +2863,20 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog)
caplog.clear()
exchange._api_async.fetch_trades.reset_mock()
res = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
res, pagid = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
if exchange._trades_pagination == 'id':
if exchange_name == 'kraken':
assert pagid == 1565798399872512133
else:
assert pagid == '126181333'
else:
assert pagid == 1565798399872
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
exchange.close()
@@ -2886,8 +2931,9 @@ 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)
res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None)
assert res[0][5] == 300
assert pagid is not None
exchange.close()
@@ -2897,13 +2943,17 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
fetch_trades_result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
if exchange._trades_pagination != 'id':
exchange.close()
pytest.skip("Exchange does not support pagination by trade id")
pagination_arg = exchange._trades_pagination_arg
async def mock_get_trade_hist(pair, *args, **kwargs):
if 'since' in kwargs:
# Return first 3
return fetch_trades_result[:-2]
elif kwargs.get('params', {}).get(pagination_arg) == fetch_trades_result[-3]['id']:
elif kwargs.get('params', {}).get(pagination_arg) in (
fetch_trades_result[-3]['id'], 1565798399752):
# Return 2
return fetch_trades_result[-3:-1]
else:
@@ -2919,7 +2969,8 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
assert isinstance(ret, tuple)
assert ret[0] == pair
assert isinstance(ret[1], list)
assert len(ret[1]) == len(fetch_trades_result)
if exchange_name != 'kraken':
assert len(ret[1]) == len(fetch_trades_result)
assert exchange._api_async.fetch_trades.call_count == 3
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
# first call (using since, not fromId)
@@ -2932,6 +2983,21 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
assert exchange._ft_has['trades_pagination_arg'] in fetch_trades_cal[1][1]['params']
@pytest.mark.parametrize('trade_id, expected', [
('1234', True),
('170544369512007228', True),
('1705443695120072285', True),
('170544369512007228555', True),
])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected):
if exchange_name == 'kraken':
pytest.skip("Kraken has a different pagination id format, and an explicit test.")
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name)
assert exchange._valid_trade_pagination_id('XRP/USDT', trade_id) == expected
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name,
@@ -2947,6 +3013,9 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
if exchange._trades_pagination != 'time':
exchange.close()
pytest.skip("Exchange does not support pagination by timestamp")
# Monkey-patch async function
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
pair = 'ETH/BTC'
@@ -2979,9 +3048,9 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
async def mock_get_trade_hist(pair, *args, **kwargs):
if kwargs['since'] == trades_history[0][0]:
return trades_history[:-1]
return trades_history[:-1], trades_history[:-1][-1][0]
else:
return []
return [], None
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
@@ -3193,7 +3262,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
mocker.patch(f'{mock_prefix}.fetch_stoploss_order', side_effect=exc)
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
assert co['amount'] == 555
assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
assert co == {'id': '_', 'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
with pytest.raises(InvalidOrderException):
exc = InvalidOrderException("Did not find order")
@@ -3410,7 +3479,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
exchange = get_patched_exchange(mocker, default_conf, id='bitpanda')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.create_stoploss(
pair='ETH/BTC',
@@ -3606,10 +3675,10 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
timeframes = ('1m', '5m', '1h')
expected = exchange._ft_has['ohlcv_candle_limit']
for timeframe in timeframes:
if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
# This should only run for bittrex
assert exchange_name == 'bittrex'
# if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
# expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
# This should only run for bittrex
# assert exchange_name == 'bittrex'
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT) == expected
@@ -4522,10 +4591,10 @@ def test_amount_to_contract_precision(
@pytest.mark.parametrize('exchange_name,open_rate,is_short,trading_mode,margin_mode', [
# Bittrex
('bittrex', 2.0, False, 'spot', None),
('bittrex', 2.0, False, 'spot', 'cross'),
('bittrex', 2.0, True, 'spot', 'isolated'),
# Bybit
('bybit', 2.0, False, 'spot', None),
('bybit', 2.0, False, 'spot', 'cross'),
('bybit', 2.0, True, 'spot', 'isolated'),
# Binance
('binance', 2.0, False, 'spot', None),
('binance', 2.0, False, 'spot', 'cross'),
@@ -4947,7 +5016,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01)
@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx', 'bybit'])
@pytest.mark.parametrize("exchange_name", ['binance', 'kraken', 'gate', 'okx', 'bybit'])
def test__get_params(mocker, default_conf, exchange_name):
api_mock = MagicMock()
mocker.patch(f'{EXMS}.exchange_has', return_value=True)
@@ -5283,3 +5352,4 @@ def test_price_to_precision_with_default_conf(default_conf, mocker):
patched_ex = get_patched_exchange(mocker, conf)
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
assert prec_price == 1.00000001
assert prec_price == 1.00000001
+16 -1
View File
@@ -10,7 +10,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (amount_to_contract_precision, amount_to_precision,
date_minus_candles, price_to_precision, timeframe_to_minutes,
timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date,
timeframe_to_seconds)
timeframe_to_resample_freq, timeframe_to_seconds)
from freqtrade.exchange.check_exchange import check_exchange
from tests.conftest import log_has_re
@@ -124,6 +124,21 @@ def test_timeframe_to_msecs():
assert timeframe_to_msecs("1d") == 86400000
@pytest.mark.parametrize("timeframe,expected", [
("1s", '1s'),
("15s", '15s'),
("5m", '300s'),
("10m", '600s'),
("1h", '3600s'),
("1d", '86400s'),
("1w", '1W-MON'),
("1M", '1MS'),
("1y", '1YS'),
])
def test_timeframe_to_resample_freq(timeframe, expected):
assert timeframe_to_resample_freq(timeframe) == expected
def test_timeframe_to_prev_date():
# 2019-08-12 13:22:08
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
@@ -14,7 +14,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
])
def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
def test_create_stoploss_order_htx(default_conf, mocker, limitratio, expected, side):
api_mock = MagicMock()
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'stop-limit'
@@ -29,7 +29,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
mocker.patch(f'{EXMS}.amount_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')
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
@@ -58,7 +58,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
@@ -69,20 +69,20 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "htx",
"create_stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
def test_create_stoploss_order_dry_run_htx(default_conf, mocker):
api_mock = MagicMock()
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, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'htx')
with pytest.raises(InvalidOrderException):
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
@@ -103,8 +103,8 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_huobi(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='huobi')
def test_stoploss_adjust_htx(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='htx')
order = {
'type': 'stop',
'price': 1500,
+35 -55
View File
@@ -13,11 +13,14 @@ STOPLOSS_ORDERTYPE = 'stop-loss'
STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit'
def test_buy_kraken_trading_agreement(default_conf, mocker):
@pytest.mark.parametrize("order_type,time_in_force,expected_params", [
('limit', 'ioc', {'timeInForce': 'IOC', 'trading_agreement': 'agree'}),
('limit', 'PO', {'postOnly': True, 'trading_agreement': 'agree'}),
('market', None, {'trading_agreement': 'agree'})
])
def test_kraken_trading_agreement(default_conf, mocker, order_type, time_in_force, expected_params):
api_mock = MagicMock()
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'limit'
time_in_force = 'ioc'
order_id = f'test_prod_{order_type}_{randint(0, 10 ** 6)}'
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -49,41 +52,9 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
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] == 200
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'IOC',
'trading_agreement': 'agree'}
assert api_mock.create_order.call_args[0][4] == (200 if order_type == 'limit' else None)
def test_sell_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
order_id = f'test_prod_sell_{randint(0, 10 ** 6)}'
order_type = 'market'
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
})
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)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
side="sell", amount=1, rate=200, leverage=1.0)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
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
assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'}
assert api_mock.create_order.call_args[0][5] == expected_params
def test_get_balances_prod(default_conf, mocker):
@@ -212,19 +183,17 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree',
'price2': adjustedprice
}
else:
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree'}
assert api_mock.create_order.call_args_list[0][1]['type'] == ordertype
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree',
'stopLossPrice': 220
}
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['price'] == adjustedprice
else:
assert api_mock.create_order.call_args_list[0][1]['price'] is None
# test exception handling
with pytest.raises(DependencyException):
@@ -282,7 +251,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
assert 'info' in order
assert 'type' in order
assert order['type'] == STOPLOSS_ORDERTYPE
assert order['type'] == 'market'
assert order['price'] == 220
assert order['amount'] == 1
@@ -294,11 +263,22 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
'type': 'market',
'stopLossPrice': 1500,
}
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(sl3, order, side=side)
# diff. order type ...
order['type'] = 'limit'
assert exchange.stoploss_adjust(sl3, order, side=side)
@pytest.mark.parametrize('trade_id, expected', [
('1234', False),
('170544369512007228', False),
('1705443695120072285', True),
('170544369512007228555', True),
])
def test__valid_trade_pagination_id_kraken(mocker, default_conf_usdt, trade_id, expected):
exchange = get_patched_exchange(mocker, default_conf_usdt, id='kraken')
assert exchange._valid_trade_pagination_id('XRP/USDT', trade_id) == expected
+1 -1
View File
@@ -247,7 +247,7 @@ EXCHANGES = {
'timeframe': '1h',
'orderbook_max_entries': 50,
},
'huobi': {
'htx': {
'pair': 'ETH/BTC',
'stake_currency': 'BTC',
'hasQuoteVolume': True,
@@ -218,9 +218,6 @@ class TestCCXTExchange:
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
exc, exchangename = exchange
if exchangename in ('bittrex'):
# For some weired reason, this test returns random lengths for bittrex.
pytest.skip("Exchange doesn't provide stable ohlcv history")
if not exc._ft_has['ohlcv_has_history']:
pytest.skip("Exchange does not support candle history")
+7 -2
View File
@@ -1,4 +1,5 @@
import platform
import sys
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict
@@ -15,6 +16,10 @@ from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
from tests.conftest import get_patched_exchange
def is_py12() -> bool:
return sys.version_info >= (3, 12)
def is_mac() -> bool:
machine = platform.system()
return "Darwin" in machine
@@ -31,7 +36,7 @@ def patch_torch_initlogs(mocker) -> None:
module_name = 'torch'
mocked_module = types.ModuleType(module_name)
sys.modules[module_name] = mocked_module
else:
elif not is_py12():
mocker.patch("torch._logging._init_logs")
@@ -54,7 +59,7 @@ def freqai_conf(default_conf, tmp_path):
"backtest_period_days": 10,
"live_retrain_hours": 0,
"expiration_hours": 1,
"identifier": "uniqe-id100",
"identifier": "unique-id100",
"live_trained_timestamp": 0,
"data_kitchen_thread_count": 2,
"activate_tensorboard": False,
+55 -4
View File
@@ -6,11 +6,17 @@ from unittest.mock import PropertyMock
import pytest
from freqtrade.commands.optimize_commands import setup_optimize_configuration
from freqtrade.configuration.timerange import TimeRange
from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import RunMode
from freqtrade.enums.candletype import CandleType
from freqtrade.exceptions import OperationalException
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has_re, patch_exchange,
patched_configuration_load_config_file)
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_patched_exchange, log_has_re,
patch_exchange, patched_configuration_load_config_file)
from tests.freqai.conftest import get_patched_freqai_strategy
def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, caplog):
@@ -40,7 +46,16 @@ def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, c
Backtesting.cleanup()
def test_freqai_backtest_load_data(freqai_conf, mocker, caplog):
@pytest.mark.parametrize(
"timeframe, expected_startup_candle_count",
[
("5m", 876),
("15m", 492),
("1d", 302),
],
)
def test_freqai_backtest_load_data(freqai_conf, mocker, caplog,
timeframe, expected_startup_candle_count):
patch_exchange(mocker)
now = datetime.now(timezone.utc)
@@ -48,10 +63,14 @@ def test_freqai_backtest_load_data(freqai_conf, mocker, caplog):
PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT']))
mocker.patch('freqtrade.optimize.backtesting.history.load_data')
mocker.patch('freqtrade.optimize.backtesting.history.get_timerange', return_value=(now, now))
freqai_conf['timeframe'] = timeframe
freqai_conf.get('freqai', {}).get('feature_parameters', {}).update({'include_timeframes': []})
backtesting = Backtesting(deepcopy(freqai_conf))
backtesting.load_bt_data()
assert log_has_re('Increasing startup_candle_count for freqai to.*', caplog)
assert log_has_re(f'Increasing startup_candle_count for freqai on {timeframe} '
f'to {expected_startup_candle_count}', caplog)
assert history.load_data.call_args[1]['startup_candles'] == expected_startup_candle_count
Backtesting.cleanup()
@@ -85,3 +104,35 @@ def test_freqai_backtest_live_models_model_not_found(freqai_conf, mocker, testda
Backtesting(bt_config)
Backtesting.cleanup()
def test_freqai_backtest_consistent_timerange(mocker, freqai_conf):
freqai_conf['runmode'] = 'backtest'
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT:USDT']))
gbs = mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
freqai_conf['candle_type_def'] = CandleType.FUTURES
freqai_conf.get('exchange', {}).update({'pair_whitelist': ['XRP/USDT:USDT']})
freqai_conf.get('freqai', {}).get('feature_parameters', {}).update(
{'include_timeframes': ['5m', '1h'], 'include_corr_pairlist': []})
freqai_conf['timerange'] = '20211120-20211121'
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.dk = FreqaiDataKitchen(freqai_conf)
timerange = TimeRange.parse_timerange("20211115-20211122")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
backtesting = Backtesting(deepcopy(freqai_conf))
backtesting.start()
gbs.call_args[1]['min_date'] == datetime(2021, 11, 20, 0, 0, tzinfo=timezone.utc)
gbs.call_args[1]['max_date'] == datetime(2021, 11, 21, 0, 0, tzinfo=timezone.utc)
Backtesting.cleanup()
+1
View File
@@ -15,6 +15,7 @@ from tests.freqai.conftest import get_patched_freqai_strategy
def test_update_historic_data(mocker, freqai_conf):
freqai_conf['runmode'] = 'backtest'
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
+63 -1
View File
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import MagicMock
import pandas as pd
import pytest
from freqtrade.configuration import TimeRange
@@ -75,7 +76,7 @@ def test_filter_features(mocker, freqai_conf):
freqai, unfiltered_dataframe = make_unfiltered_dataframe(mocker, freqai_conf)
freqai.dk.find_features(unfiltered_dataframe)
filtered_df, labels = freqai.dk.filter_features(
filtered_df, _labels = freqai.dk.filter_features(
unfiltered_dataframe,
freqai.dk.training_features_list,
freqai.dk.label_list,
@@ -135,3 +136,64 @@ def test_get_full_model_path(mocker, freqai_conf, model):
model_path = freqai.dk.get_full_models_path(freqai_conf)
assert model_path.is_dir() is True
def test_get_pair_data_for_features_with_prealoaded_data(mocker, freqai_conf):
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.dk = FreqaiDataKitchen(freqai_conf)
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
_, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
assert df is base_df["5m"]
assert not df.empty
def test_get_pair_data_for_features_without_preloaded_data(mocker, freqai_conf):
freqai_conf.update({"timerange": "20180115-20180130"})
freqai_conf['runmode'] = 'backtest'
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.dk = FreqaiDataKitchen(freqai_conf)
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
base_df = {'5m': pd.DataFrame()}
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
assert df is not base_df["5m"]
assert not df.empty
assert df.iloc[0]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-11 23:00:00"
assert df.iloc[-1]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-30 00:00:00"
def test_populate_features(mocker, freqai_conf):
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
strategy.freqai_info = freqai_conf.get("freqai", {})
freqai = strategy.freqai
freqai.dk = FreqaiDataKitchen(freqai_conf)
timerange = TimeRange.parse_timerange("20180115-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
mocker.patch.object(strategy, 'feature_engineering_expand_all', return_value=base_df["5m"])
df = freqai.dk.populate_features(base_df["5m"], "LTC/BTC", strategy,
base_dataframes=base_df, corr_dataframes=corr_df)
strategy.feature_engineering_expand_all.assert_called_once()
pd.testing.assert_frame_equal(base_df["5m"],
strategy.feature_engineering_expand_all.call_args[0][0])
assert df.iloc[0]['date'].strftime("%Y-%m-%d %H:%M:%S") == "2018-01-15 00:00:00"
+16 -11
View File
@@ -1,7 +1,6 @@
import logging
import platform
import shutil
import sys
from pathlib import Path
from unittest.mock import MagicMock
@@ -16,24 +15,24 @@ 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, is_mac, make_rl_config,
from tests.freqai.conftest import (get_patched_freqai_strategy, is_mac, is_py12, make_rl_config,
mock_pytorch_mlp_model_training_parameters)
def is_py11() -> bool:
return sys.version_info >= (3, 11)
def is_arm() -> bool:
machine = platform.machine()
return "arm" in machine or "aarch64" in machine
def can_run_model(model: str) -> None:
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
if is_py12() and ("Catboost" in model or is_pytorch_model):
pytest.skip("Model not supported on python 3.12 yet.")
if is_arm() and "Catboost" in model:
pytest.skip("CatBoost is not supported on ARM.")
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.")
@@ -176,6 +175,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
'CatboostClassifier',
'XGBoostClassifier',
'XGBoostRFClassifier',
'SKLearnRandomForestClassifier',
'PyTorchMLPClassifier',
])
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
@@ -298,8 +298,11 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
def test_start_backtesting_subdaily_backtest_period(mocker, freqai_conf):
freqai_conf.update({"timerange": "20180120-20180124"})
freqai_conf.get("freqai", {}).update({"backtest_period_days": 0.5})
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
freqai_conf['runmode'] = 'backtest'
freqai_conf.get("freqai", {}).update({
"backtest_period_days": 0.5,
"save_backtest_models": True,
})
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"indicator_periods_candles": [2]})
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
@@ -326,6 +329,7 @@ def test_start_backtesting_subdaily_backtest_period(mocker, freqai_conf):
def test_start_backtesting_from_existing_folder(mocker, freqai_conf, caplog):
freqai_conf.update({"timerange": "20180120-20180130"})
freqai_conf['runmode'] = 'backtest'
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"indicator_periods_candles": [2]})
@@ -389,6 +393,7 @@ def test_start_backtesting_from_existing_folder(mocker, freqai_conf, caplog):
def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
freqai_conf['runmode'] = 'backtest'
freqai_conf.get("freqai", {}).update({"fit_live_predictions_candles": 10})
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
@@ -522,8 +527,8 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers):
if is_mac():
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
if is_py11():
pytest.skip("Reinforcement learning currently not available on python 3.11.")
if is_py12():
pytest.skip("Reinforcement learning currently not available on python 3.12.")
freqai_conf.update({"freqaimodel": "ReinforcementLearner"})
freqai_conf.update({"timerange": "20180110-20180130"})
+12 -5
View File
@@ -734,7 +734,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
'enter_tag': [None, None],
'enter_tag': ['', ''],
"leverage": [1.0, 1.0],
"is_short": [False, False],
'open_timestamp': [1517251200000, 1517283000000],
@@ -742,14 +742,18 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'orders': [
[
{'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True},
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True,
'ft_order_tag': ''},
{'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False}
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False,
'ft_order_tag': 'roi'}
], [
{'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True},
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True,
'ft_order_tag': ''},
{'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False}
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False,
'ft_order_tag': 'roi'}
]
]
})
@@ -1132,6 +1136,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
default_conf['runmode'] = 'backtest'
mocker.patch(f'{EXMS}.get_fee', fee)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000)
@@ -1298,6 +1303,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch(f'{EXMS}.get_fee', fee)
default_conf['max_open_trades'] = 10
default_conf['runmode'] = 'backtest'
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m'
@@ -1342,6 +1348,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
dataframe['exit_short'] = 0
return dataframe
default_conf['runmode'] = 'backtest'
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_fee', fee)
@@ -72,7 +72,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10481985, 0.1038888],
'is_open': [False, False],
'enter_tag': [None, None],
'enter_tag': ['', ''],
'leverage': [1.0, 1.0],
'is_short': [False, False],
'open_timestamp': [1517251200000, 1517283000000],
@@ -148,7 +148,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 1
# Increase position by 100
backtesting.strategy.adjust_trade_position = MagicMock(return_value=100)
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, 'PartIncrease'))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
@@ -156,6 +156,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.stake_amount) == 200.0
assert pytest.approx(trade.amount) == 95.23809524 * leverage
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_tag == 'PartIncrease'
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
# Reduce by more than amount - no change to trade.
@@ -171,13 +172,14 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
# Reduce position by 50
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100)
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, 'partDecrease'))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 100.0
assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_tag == 'partDecrease'
assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
+10 -10
View File
@@ -254,7 +254,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
'is_best': True
}
)
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert all(x in out
for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"])
@@ -333,7 +333,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
# Should be called for historical candle data
assert dumper.call_count == 1
@@ -577,7 +577,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
result_str = (
'{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi"'
':{},"stoploss":null,"trailing_stop":null,"max_open_trades":null}'
@@ -624,7 +624,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
# Should be called for historical candle data
assert dumper.call_count == 1
@@ -666,7 +666,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert '{"minimal_roi":{},"stoploss":null}' in out
assert dumper.call_count == 1
@@ -704,7 +704,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.call_count == 1
assert dumper2.call_count == 1
@@ -777,7 +777,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
assert dumper.call_count == 1
@@ -819,7 +819,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
parallel.assert_called_once()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
assert dumper.call_count == 1
@@ -1051,7 +1051,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
hyperopt.start()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'max_open_trades = -1' in out
assert 'max_open_trades = inf' not in out
@@ -1070,7 +1070,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
hyperopt.start()
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert '"max_open_trades":-1' in out
+9 -9
View File
@@ -143,8 +143,8 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
instance.current_analysis = analysis
table, headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
_table, _headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
# check row contents for a try that has too few signals
assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py'
@@ -158,14 +158,14 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
analysis.false_exit_signals = 10
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
instance.current_analysis = analysis
table, headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
_table, _headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
assert data[0][2].__contains__("error")
# edit it into not showing an error
instance.failed_bias_check = False
table, headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
_table, _headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py'
assert data[0][1] == 'strategy_test_v3_with_lookahead_bias'
assert data[0][2] # True
@@ -176,8 +176,8 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
analysis.false_indicators.append('falseIndicator1')
analysis.false_indicators.append('falseIndicator2')
table, headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
_table, _headers, data = (LookaheadAnalysisSubFunctions.
text_table_lookahead_analysis_instances(lookahead_conf, [instance]))
assert data[0][6] == 'falseIndicator1, falseIndicator2'
@@ -185,7 +185,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
assert len(data) == 1
# check amount of multiple rows
table, headers, data = (LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
_table, _headers, data = (LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance, instance, instance]))
assert len(data) == 3
+1 -1
View File
@@ -513,7 +513,7 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
show_sorted_pairlist(default_conf, bt_data)
out, err = capsys.readouterr()
out, _err = capsys.readouterr()
assert 'Pairs for Strategy StrategyTestV3: \n[' in out
assert 'TOTAL' not in out
assert '"ETH/BTC", // ' in out
+4 -4
View File
@@ -107,8 +107,8 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
instance.dict_recursive = dict_diff
table, headers, data = (RecursiveAnalysisSubFunctions.
text_table_recursive_analysis_instances([instance]))
_table, _headers, data = (RecursiveAnalysisSubFunctions.
text_table_recursive_analysis_instances([instance]))
# check row contents for a try that has too few signals
assert data[0][0] == 'rsi'
@@ -119,8 +119,8 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
dict_diff = dict()
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
instance.dict_recursive = dict_diff
table, headers, data = (RecursiveAnalysisSubFunctions.
text_table_recursive_analysis_instances([instance]))
_table, _headers, data = (RecursiveAnalysisSubFunctions.
text_table_recursive_analysis_instances([instance]))
assert len(data) == 0
+24
View File
@@ -0,0 +1,24 @@
import pytest
from freqtrade.persistence import FtNoDBContext, PairLocks, Trade
@pytest.mark.parametrize('timeframe', ['', '5m', '1d'])
def test_FtNoDBContext(timeframe):
PairLocks.timeframe = ''
assert Trade.use_db is True
assert PairLocks.use_db is True
assert PairLocks.timeframe == ''
with FtNoDBContext(timeframe):
assert Trade.use_db is False
assert PairLocks.use_db is False
assert PairLocks.timeframe == timeframe
with FtNoDBContext():
assert Trade.use_db is False
assert PairLocks.use_db is False
assert PairLocks.timeframe == ''
assert Trade.use_db is True
assert PairLocks.use_db is True
+142
View File
@@ -18,6 +18,7 @@ from freqtrade.persistence import LocalTrade, Trade
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import PairListResolver
from freqtrade.util.datetime_helpers import dt_now
from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange,
get_patched_freqtradebot, log_has, log_has_re, num_log_has)
@@ -1513,3 +1514,144 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
pm.refresh_pairlist()
assert pm.whitelist == []
assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog)
@pytest.mark.parametrize('pairlists,trade_mode,result', [
([
# Get 2 pairs
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "number_assets": 2}
], 'spot', ['BTC/USDT', 'ETH/USDT']),
([
# Get 6 pairs
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "number_assets": 6}
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'ADA/USDT']),
([
# Get 3 pairs within top 6 ranks
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "max_rank": 6, "number_assets": 3}
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
([
# Get 4 pairs within top 8 ranks
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "max_rank": 8, "number_assets": 4}
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
([
# MarketCapPairList as generator
{"method": "MarketCapPairList", "number_assets": 5}
], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']),
([
# MarketCapPairList as generator - low max_rank
{"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5}
], 'spot', ['BTC/USDT', 'ETH/USDT']),
([
# MarketCapPairList as generator - futures - low max_rank
{"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5}
], 'futures', ['ETH/USDT:USDT']),
([
# MarketCapPairList as generator - futures - low number_assets
{"method": "MarketCapPairList", "number_assets": 2}
], 'futures', ['ETH/USDT:USDT', 'ADA/USDT:USDT']),
])
def test_MarketCapPairList_filter(
mocker, default_conf_usdt, trade_mode, markets, pairlists, result
):
test_value = [
{"symbol": "btc"},
{"symbol": "eth"},
{"symbol": "usdt"},
{"symbol": "bnb"},
{"symbol": "sol"},
{"symbol": "xrp"},
{"symbol": "usdc"},
{"symbol": "steth"},
{"symbol": "ada"},
{"symbol": "avax"},
]
default_conf_usdt['trading_mode'] = trade_mode
if trade_mode == 'spot':
default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT'])
default_conf_usdt['pairlists'] = pairlists
mocker.patch.multiple(EXMS,
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True),
)
mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
return_value=test_value)
exchange = get_patched_exchange(mocker, default_conf_usdt)
pm = PairListManager(exchange, default_conf_usdt)
pm.refresh_pairlist()
assert pm.whitelist == result
def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machine):
test_value = [
{"symbol": "btc"},
{"symbol": "eth"},
{"symbol": "usdt"},
{"symbol": "bnb"},
{"symbol": "sol"},
{"symbol": "xrp"},
{"symbol": "usdc"},
{"symbol": "steth"},
{"symbol": "ada"},
{"symbol": "avax"},
]
default_conf_usdt['trading_mode'] = 'spot'
default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT'])
default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList", "number_assets": 2}]
markets_mock = MagicMock(return_value=markets)
mocker.patch.multiple(EXMS,
get_markets=markets_mock,
exchange_has=MagicMock(return_value=True),
)
mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets",
return_value=test_value)
start_dt = dt_now()
exchange = get_patched_exchange(mocker, default_conf_usdt)
time_machine.move_to(start_dt)
pm = PairListManager(exchange, default_conf_usdt)
markets_mock.reset_mock()
pm.refresh_pairlist()
assert markets_mock.call_count == 3
markets_mock.reset_mock()
time_machine.move_to(start_dt + timedelta(hours=20))
pm.refresh_pairlist()
# Cached pairlist ...
assert markets_mock.call_count == 1
markets_mock.reset_mock()
time_machine.move_to(start_dt + timedelta(days=2))
pm.refresh_pairlist()
# No longer cached pairlist ...
assert markets_mock.call_count == 3
def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, markets, time_machine):
exchange = get_patched_exchange(mocker, default_conf_usdt)
default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList"}]
with pytest.raises(OperationalException, match=r"`number_assets` not specified.*"):
# No number_assets
PairListManager(exchange, default_conf_usdt)
default_conf_usdt['pairlists'] = [{
"method": "MarketCapPairList", 'number_assets': 20, 'max_rank': 260
}]
with pytest.raises(OperationalException,
match="This filter only support marketcap rank up to 250."):
PairListManager(exchange, default_conf_usdt)
+7 -5
View File
@@ -82,7 +82,7 @@ def test_fetch_pairlist_mock_response_html(mocker, rpl_config):
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
rpl_config['pairlists'][0], 0)
with pytest.raises(OperationalException, match='RemotePairList is not of type JSON, abort.'):
with pytest.raises(OperationalException, match='RemotePairList is not of type JSON.'):
remote_pairlist.fetch_pairlist()
@@ -107,9 +107,11 @@ def test_fetch_pairlist_timeout_keep_last_pairlist(mocker, rpl_config, caplog):
rpl_config['pairlists'][0], 0)
remote_pairlist._last_pairlist = ["BTC/USDT", "ETH/USDT", "LTC/USDT"]
remote_pairlist._init_done = True
pairlist_url = rpl_config['pairlists'][0]['pairlist_url']
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
assert log_has(f"Was not able to fetch pairlist from: {remote_pairlist._pairlist_url}", caplog)
assert log_has(f'Error: Was not able to fetch pairlist from: ' f'{pairlist_url}', caplog)
assert log_has("Keeping last fetched pairlist", caplog)
assert pairs == ["BTC/USDT", "ETH/USDT", "LTC/USDT"]
@@ -281,7 +283,7 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers)
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
rpl_config["pairlists"][1], 1)
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
assert pairs == ["XRP/USDT"]
@@ -334,7 +336,7 @@ def test_remote_pairlist_whitelist(mocker, rpl_config, processing_mode, markets,
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
rpl_config["pairlists"][1], 1)
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
pairs, _time_elapsed = remote_pairlist.fetch_pairlist()
assert pairs == ["XRP/USDT"]
+15 -5
View File
@@ -9,7 +9,7 @@ from sqlalchemy import select
from freqtrade.edge import PairInfo
from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.persistence import Order, Trade
from freqtrade.persistence.pairlock_middleware import PairLocks
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -99,7 +99,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None,
'funding_fee': ANY,
'funding_fee': ANY, 'ft_order_tag': None,
}],
}
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -355,8 +355,18 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
rpc._rpc_delete('200')
trades = Trade.session.scalars(select(Trade)).all()
trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '102'
trades[2].orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trades[2].pair,
ft_is_open=True,
ft_amount=trades[2].amount,
ft_price=trades[2].stop_loss,
order_id='102',
status='open',
)
)
assert len(trades) > 2
res = rpc._rpc_delete('1')
@@ -369,7 +379,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
cancel_mock.reset_mock()
stoploss_mock.reset_mock()
res = rpc._rpc_delete('2')
res = rpc._rpc_delete('5')
assert isinstance(res, dict)
assert stoploss_mock.call_count == 1
assert res['cancel_order_count'] == 1
+24 -23
View File
@@ -112,7 +112,7 @@ def assert_response(response, expected_code=200, needs_cors=True):
def test_api_not_found(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/invalid_url")
assert_response(rc, 404)
@@ -120,7 +120,7 @@ def test_api_not_found(botclient):
def test_api_ui_fallback(botclient, mocker):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, "/favicon.ico")
assert rc.status_code == 200
@@ -150,7 +150,7 @@ def test_api_ui_fallback(botclient, mocker):
def test_api_ui_version(botclient, mocker):
ftbot, client = botclient
_ftbot, client = botclient
mocker.patch('freqtrade.commands.deploy_commands.read_ui_version', return_value='0.1.2')
rc = client_get(client, "/ui_version")
@@ -230,7 +230,7 @@ def test_api_unauthorized(botclient):
def test_api_token_login(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client.post(f"{BASE_URI}/token/login",
data=None,
headers={'Authorization': _basic_auth_str('WRONG_USER', 'WRONG_PASS'),
@@ -249,7 +249,7 @@ def test_api_token_login(botclient):
def test_api_token_refresh(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_post(client, f"{BASE_URI}/token/login")
assert_response(rc)
rc = client.post(f"{BASE_URI}/token/refresh",
@@ -541,7 +541,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short):
def test_api_locks(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
@@ -728,7 +728,6 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
ftbot.strategy.order_types['stoploss_on_exchange'] = True
trades = Trade.session.scalars(select(Trade)).all()
trades[1].stoploss_order_id = '1234'
Trade.commit()
assert len(trades) > 2
@@ -745,9 +744,9 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
assert cancel_mock.call_count == 0
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
rc = client_delete(client, f"{BASE_URI}/trades/2")
rc = client_delete(client, f"{BASE_URI}/trades/5")
assert_response(rc)
assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 1 open orders.'
assert rc.json()['result_msg'] == 'Deleted trade 5. Closed 1 open orders.'
assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all())
assert stoploss_mock.call_count == 1
@@ -822,7 +821,7 @@ def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_shor
def test_api_logs(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/logs")
assert_response(rc)
assert len(rc.json()) == 2
@@ -1229,7 +1228,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
def test_api_version(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/version")
assert_response(rc)
@@ -1237,7 +1236,7 @@ def test_api_version(botclient):
def test_api_blacklist(botclient, mocker):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/blacklist")
assert_response(rc)
@@ -1304,7 +1303,7 @@ def test_api_blacklist(botclient, mocker):
def test_api_whitelist(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/whitelist")
assert_response(rc)
@@ -1559,7 +1558,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
def test_api_pair_history(botclient, mocker):
ftbot, client = botclient
_ftbot, client = botclient
timeframe = '5m'
lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
# No pair
@@ -1604,9 +1603,9 @@ def test_api_pair_history(botclient, mocker):
assert 'data' in result
data = result['data']
assert len(data) == 289
# analyed DF has 28 columns
assert len(result['columns']) == 28
assert len(data[0]) == 28
# analyed DF has 30 columns
assert len(result['columns']) == 30
assert len(data[0]) == 30
date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0]
rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0]
@@ -1699,7 +1698,7 @@ def test_api_strategies(botclient, tmp_path):
def test_api_strategy(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
@@ -1718,7 +1717,7 @@ def test_api_strategy(botclient):
def test_api_exchanges(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/exchanges")
assert_response(rc)
@@ -1770,6 +1769,7 @@ def test_api_freqaimodels(botclient, tmp_path, mocker):
{'name': 'LightGBMRegressorMultiTarget'},
{'name': 'ReinforcementLearner'},
{'name': 'ReinforcementLearner_multiproc'},
{'name': 'SKlearnRandomForestClassifier'},
{'name': 'XGBoostClassifier'},
{'name': 'XGBoostRFClassifier'},
{'name': 'XGBoostRFRegressor'},
@@ -1788,6 +1788,7 @@ def test_api_freqaimodels(botclient, tmp_path, mocker):
'LightGBMRegressorMultiTarget',
'ReinforcementLearner',
'ReinforcementLearner_multiproc',
'SKlearnRandomForestClassifier',
'XGBoostClassifier',
'XGBoostRFClassifier',
'XGBoostRFRegressor',
@@ -1953,7 +1954,7 @@ def test_list_available_pairs(botclient):
def test_sysinfo(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/sysinfo")
assert_response(rc)
@@ -2233,7 +2234,7 @@ def test_api_patch_backtest_history_entry(botclient, tmp_path: Path):
def test_health(botclient):
ftbot, client = botclient
_ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/health")
@@ -2244,7 +2245,7 @@ def test_health(botclient):
def test_api_ws_subscribe(botclient, mocker):
ftbot, client = botclient
_ftbot, client = botclient
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
sub_mock = mocker.patch('freqtrade.rpc.api_server.ws.WebSocketChannel.set_subscriptions')
@@ -2267,7 +2268,7 @@ def test_api_ws_subscribe(botclient, mocker):
def test_api_ws_requests(botclient, caplog):
caplog.set_level(logging.DEBUG)
ftbot, client = botclient
_ftbot, client = botclient
ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}"
# Test whitelist request
+9 -20
View File
@@ -2,7 +2,6 @@
Unit test file for rpc/external_message_consumer.py
"""
import asyncio
import functools
import logging
from datetime import datetime, timezone
from unittest.mock import MagicMock
@@ -302,19 +301,16 @@ async def test_emc_receive_messages_valid(default_conf, caplog, mocker):
dp = DataProvider(default_conf, None, None, None)
emc = ExternalMessageConsumer(default_conf, dp)
loop = asyncio.get_event_loop()
def change_running(emc): emc._running = not emc._running
class TestChannel:
async def recv(self, *args, **kwargs):
emc._running = False
return {"type": "whitelist", "data": ["BTC/USDT"]}
async def ping(self, *args, **kwargs):
return asyncio.Future()
try:
change_running(emc)
loop.call_soon(functools.partial(change_running, emc=emc))
emc._running = True
await emc._receive_messages(TestChannel(), test_producer, lock)
assert log_has_re(r"Received message of type `whitelist`.+", caplog)
@@ -349,19 +345,16 @@ async def test_emc_receive_messages_invalid(default_conf, caplog, mocker):
dp = DataProvider(default_conf, None, None, None)
emc = ExternalMessageConsumer(default_conf, dp)
loop = asyncio.get_event_loop()
def change_running(emc): emc._running = not emc._running
class TestChannel:
async def recv(self, *args, **kwargs):
emc._running = False
return {"type": ["BTC/USDT"]}
async def ping(self, *args, **kwargs):
return asyncio.Future()
try:
change_running(emc)
loop.call_soon(functools.partial(change_running, emc=emc))
emc._running = True
await emc._receive_messages(TestChannel(), test_producer, lock)
assert log_has_re(r"Invalid message from.+", caplog)
@@ -396,8 +389,8 @@ async def test_emc_receive_messages_timeout(default_conf, caplog, mocker):
dp = DataProvider(default_conf, None, None, None)
emc = ExternalMessageConsumer(default_conf, dp)
loop = asyncio.get_event_loop()
def change_running(emc): emc._running = not emc._running
def change_running():
emc._running = not emc._running
class TestChannel:
async def recv(self, *args, **kwargs):
@@ -407,8 +400,7 @@ async def test_emc_receive_messages_timeout(default_conf, caplog, mocker):
return asyncio.Future()
try:
change_running(emc)
loop.call_soon(functools.partial(change_running, emc=emc))
change_running()
with pytest.raises(asyncio.TimeoutError):
await emc._receive_messages(TestChannel(), test_producer, lock)
@@ -447,19 +439,16 @@ async def test_emc_receive_messages_handle_error(default_conf, caplog, mocker):
emc.handle_producer_message = MagicMock(side_effect=Exception)
loop = asyncio.get_event_loop()
def change_running(emc): emc._running = not emc._running
class TestChannel:
async def recv(self, *args, **kwargs):
emc._running = False
return {"type": "whitelist", "data": ["BTC/USDT"]}
async def ping(self, *args, **kwargs):
return asyncio.Future()
try:
change_running(emc)
loop.call_soon(functools.partial(change_running, emc=emc))
emc._running = True
await emc._receive_messages(TestChannel(), test_producer, lock)
assert log_has_re(r"Error handling producer message.+", caplog)
+120 -82
View File
@@ -599,7 +599,7 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time
get_fee=fee,
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
# Move date to within day
time_machine.move_to('2022-06-11 08:00:00+00:00')
@@ -1154,11 +1154,11 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
'profit_amount': 6.314e-05,
'profit_ratio': 0.0629778,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_EXIT.value,
'exit_reason': ExitType.FORCE_EXIT.value,
'open_date': ANY,
'close_date': ANY,
@@ -1166,6 +1166,8 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
'stake_amount': 0.0009999999999054,
'sub_trade': False,
'cumulative_profit': 0.0,
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -1227,11 +1229,11 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee
'profit_amount': -5.497e-05,
'profit_ratio': -0.05482878,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_EXIT.value,
'exit_reason': ExitType.FORCE_EXIT.value,
'open_date': ANY,
'close_date': ANY,
@@ -1239,6 +1241,8 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee
'stake_amount': 0.0009999999999054,
'sub_trade': False,
'cumulative_profit': 0.0,
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -1290,11 +1294,11 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -
'profit_amount': -4.09e-06,
'profit_ratio': -0.00408133,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_EXIT.value,
'exit_reason': ExitType.FORCE_EXIT.value,
'open_date': ANY,
'close_date': ANY,
@@ -1302,6 +1306,8 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -
'stake_amount': 0.0009999999999054,
'sub_trade': False,
'cumulative_profit': 0.0,
'is_final_exit': False,
'final_profit_ratio': None,
} == msg
@@ -1474,7 +1480,7 @@ async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fe
fetch_ticker=ticker,
get_fee=fee,
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
# Create some test data
create_mock_trades_usdt(fee)
@@ -1649,7 +1655,7 @@ async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -
async def test_whitelist_static(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
await telegram._whitelist(update=update, context=MagicMock())
assert msg_mock.call_count == 1
@@ -1999,7 +2005,10 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sub_trade': False,
'current_rate': 1.099e-05,
'amount': 1333.3333333333335,
'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5},
@@ -2008,17 +2017,19 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg(msg)
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance (dry):* {enter} ETH/BTC (#1)\n'
f'\N{LARGE BLUE CIRCLE} *Binance (dry):* New Trade (#1)\n'
f'*Pair:* `ETH/BTC`\n'
'*Candle OHLC*: `1.1, 2.2, 1.0, 1.5`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
f'*Direction:* `{enter}'
f'{leverage_text}`\n'
'*Open Rate:* `0.00001099 BTC`\n'
'*Current Rate:* `0.00001099 BTC`\n'
'*Total:* `0.01465333 BTC / 180.895 USD`'
)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
@@ -2106,20 +2117,25 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
'leverage': leverage,
'stake_amount': 0.01465333,
'direction': entered,
'sub_trade': False,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'open_rate': 1.099e-05,
'amount': 1333.3333333333335,
'open_date': dt_now() - timedelta(hours=1)
})
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
leverage_text = f' ({leverage:.1g}x)' if leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{CHECK MARK} *Binance (dry):* {entered}ed ETH/BTC (#1)\n'
f'\N{CHECK MARK} *Binance (dry):* New Trade filled (#1)\n'
f'*Pair:* `ETH/BTC`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f"{leverage_text}"
'*Open Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
f'*Direction:* `{entered}'
f"{leverage_text}`\n"
'*Open Rate:* `0.00001099 BTC`\n'
'*Total:* `0.01465333 BTC / 180.895 USD`'
)
msg_mock.reset_mock()
@@ -2134,6 +2150,8 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
'sub_trade': True,
'direction': entered,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'open_rate': 1.099e-05,
'amount': 1333.3333333333335,
@@ -2141,16 +2159,18 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
})
assert msg_mock.call_args[0][0] == (
f'\N{CHECK MARK} *Binance (dry):* {entered}ed ETH/BTC (#1)\n'
f'\N{CHECK MARK} *Binance (dry):* Position increase filled (#1)\n'
f'*Pair:* `ETH/BTC`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f"{leverage_text}"
'*Open Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
f'*Direction:* `{entered}'
f"{leverage_text}`\n"
'*Open Rate:* `0.00001099 BTC`\n'
'*New Total:* `0.01465333 BTC / 180.895 USD`'
)
def test_send_msg_sell_notification(default_conf, mocker) -> None:
def test_send_msg_exit_notification(default_conf, mocker) -> None:
with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -2165,14 +2185,16 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'leverage': 1.0,
'direction': 'Long',
'gain': 'loss',
'order_rate': 3.201e-05,
'order_rate': 3.201e-04,
'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05,
'current_rate': 3.201e-05,
'open_rate': 7.5e-04,
'current_rate': 3.201e-04,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'quote_currency': 'ETH',
'base_currency': 'KEY',
'fiat_currency': 'USD',
'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value,
@@ -2181,14 +2203,14 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
'*Enter Tag:* `buy_signal1`\n'
'*Exit Reason:* `stop_loss`\n'
'*Direction:* `Long`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Exit Rate:* `0.00003201`\n'
'*Open Rate:* `0.00075 ETH`\n'
'*Current Rate:* `0.00032 ETH`\n'
'*Exit Rate:* `0.00032 ETH`\n'
'*Duration:* `1:00:00 (60.0 min)`'
)
@@ -2200,15 +2222,17 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH',
'direction': 'Long',
'gain': 'loss',
'order_rate': 3.201e-05,
'order_rate': 3.201e-04,
'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05,
'current_rate': 3.201e-05,
'open_rate': 7.5e-04,
'current_rate': 3.201e-04,
'cumulative_profit': -0.15746268,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'quote_currency': 'ETH',
'base_currency': 'KEY',
'fiat_currency': 'USD',
'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value,
@@ -2219,16 +2243,16 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n'
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
'*Cumulative Profit:* `-0.15746 ETH / -24.812 USD`\n'
'*Enter Tag:* `buy_signal1`\n'
'*Exit Reason:* `stop_loss`\n'
'*Direction:* `Long`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Exit Rate:* `0.00003201`\n'
'*Remaining:* `(0.01 ETH, -24.812 USD)`'
'*Open Rate:* `0.00075 ETH`\n'
'*Current Rate:* `0.00032 ETH`\n'
'*Exit Rate:* `0.00032 ETH`\n'
'*Remaining:* `0.01 ETH / -24.812 USD`'
)
msg_mock.reset_mock()
@@ -2239,14 +2263,17 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH',
'direction': 'Long',
'gain': 'loss',
'order_rate': 3.201e-05,
'order_rate': 3.201e-04,
'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05,
'current_rate': 3.201e-05,
'open_rate': 7.5e-04,
'current_rate': 3.201e-04,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'quote_currency': 'ETH',
'base_currency': 'KEY',
'fiat_currency': None,
'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
@@ -2254,21 +2281,21 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
'*Enter Tag:* `buy_signal1`\n'
'*Exit Reason:* `stop_loss`\n'
'*Direction:* `Long`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Exit Rate:* `0.00003201`\n'
'*Open Rate:* `0.00075 ETH`\n'
'*Current Rate:* `0.00032 ETH`\n'
'*Exit Rate:* `0.00032 ETH`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
)
# Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount
async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
async def test_send_msg_exit_cancel_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -2306,7 +2333,7 @@ async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0)])
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
def test_send_msg_exit_fill_notification(default_conf, mocker, direction,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
@@ -2321,31 +2348,34 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
'leverage': leverage,
'direction': direction,
'gain': 'loss',
'limit': 3.201e-05,
'limit': 3.201e-04,
'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05,
'close_rate': 3.201e-05,
'open_rate': 7.5e-04,
'close_rate': 3.201e-04,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'quote_currency': 'ETH',
'base_currency': 'KEY',
'fiat_currency': None,
'enter_tag': enter_signal,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
'close_date': dt_now(),
})
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
leverage_text = f' ({leverage:.1g}x)`\n' if leverage and leverage != 1.0 else '`\n'
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
'*Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Exit Reason:* `stop_loss`\n'
f"*Direction:* `{direction}`\n"
f"*Direction:* `{direction}"
f"{leverage_text}"
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Exit Rate:* `0.00003201`\n'
'*Open Rate:* `0.00075 ETH`\n'
'*Exit Rate:* `0.00032 ETH`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
)
@@ -2416,24 +2446,29 @@ def test_send_msg_buy_notification_no_fiat(
'open_rate': 1.099e-05,
'order_type': 'limit',
'direction': enter,
'sub_trade': False,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'quote_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': None,
'current_rate': 1.099e-05,
'amount': 1333.3333333333335,
'open_date': dt_now() - timedelta(hours=1)
})
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
f'\N{LARGE BLUE CIRCLE} *Binance:* New Trade (#1)\n'
'*Pair:* `ETH/BTC`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC)`'
f'*Direction:* `{enter}'
f'{leverage_text}`\n'
'*Open Rate:* `0.00001099 BTC`\n'
'*Current Rate:* `0.00001099 BTC`\n'
'*Total:* `0.01465333 BTC`'
)
@@ -2443,7 +2478,7 @@ def test_send_msg_buy_notification_no_fiat(
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
def test_send_msg_exit_notification_no_fiat(
default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
del default_conf['fiat_display_currency']
time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
@@ -2457,14 +2492,17 @@ def test_send_msg_sell_notification_no_fiat(
'gain': 'loss',
'leverage': leverage,
'direction': direction,
'order_rate': 3.201e-05,
'sub_trade': False,
'order_rate': 3.201e-04,
'amount': 1333.3333333333335,
'order_type': 'limit',
'open_rate': 7.5e-05,
'current_rate': 3.201e-05,
'open_rate': 7.5e-04,
'current_rate': 3.201e-04,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'quote_currency': 'ETH',
'base_currency': 'KEY',
'fiat_currency': 'USD',
'enter_tag': enter_signal,
'exit_reason': ExitType.STOP_LOSS.value,
@@ -2472,37 +2510,37 @@ def test_send_msg_sell_notification_no_fiat(
'close_date': dt_now(),
})
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Exit Reason:* `stop_loss`\n'
f'*Direction:* `{direction}`\n'
f'{leverage_text}'
f'*Direction:* `{direction}'
f'{leverage_text}`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Exit Rate:* `0.00003201`\n'
'*Open Rate:* `0.00075 ETH`\n'
'*Current Rate:* `0.00032 ETH`\n'
'*Exit Rate:* `0.00032 ETH`\n'
'*Duration:* `2:35:03 (155.1 min)`'
)
@pytest.mark.parametrize('msg,expected', [
({'profit_percent': 20.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 5.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 2.56, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 1.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': -5.0, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
({'profit_percent': -2.0, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
({'profit_ratio': 0.201, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_ratio': 0.051, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_ratio': 0.0256, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_ratio': 0.01, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_ratio': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_ratio': -0.05, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
({'profit_ratio': -0.02, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
])
def test__sell_emoji(default_conf, mocker, msg, expected):
def test__exit_emoji(default_conf, mocker, msg, expected):
del default_conf['fiat_display_currency']
telegram, _, _ = get_telegram_testobject(mocker, default_conf)
assert telegram._get_sell_emoji(msg) == expected
assert telegram._get_exit_emoji(msg) == expected
async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
@@ -2609,7 +2647,7 @@ async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
async def test_change_market_direction(default_conf, mocker, update) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram, _, _msg_mock = get_telegram_testobject(mocker, default_conf)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE
context = MagicMock()
context.args = ["long"]
+2
View File
@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103, protected-access
import logging
from datetime import datetime, timedelta
from unittest.mock import MagicMock
@@ -331,6 +332,7 @@ def test_send_msg_webhook(default_conf, mocker):
def test_exception_send_msg(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
default_conf["webhook"] = get_webhook_dict()
del default_conf["webhook"]["entry"]
del default_conf["webhook"]["webhookentry"]
+2 -2
View File
@@ -152,7 +152,7 @@ class StrategyTestV3(IStrategy):
(
qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)
),
'enter_short'] = 1
('enter_short', 'enter_tag')] = (1, 'short_Tag')
return dataframe
@@ -176,7 +176,7 @@ class StrategyTestV3(IStrategy):
(
qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)
),
'exit_short'] = 1
('exit_short', 'exit_tag')] = (1, 'short_Tag')
return dataframe
+28 -1
View File
@@ -105,7 +105,7 @@ def test_returns_latest_signal(ohlcv_history):
_STRATEGY.config['trading_mode'] = 'spot'
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
def test_analyze_pair_empty(mocker, caplog, ohlcv_history):
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
mocker.patch.object(
_STRATEGY, '_analyze_ticker_internal',
@@ -1019,3 +1019,30 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
StrategyResolver.load_strategy(default_conf)
assert log_has("Invalid parameter file format.", caplog)
@pytest.mark.parametrize('function,raises', [
('populate_entry_trend', True),
('advise_entry', False),
('populate_exit_trend', True),
('advise_exit', False),
])
def test_pandas_warning_direct(ohlcv_history, function, raises):
df = _STRATEGY.populate_indicators(ohlcv_history, {'pair': 'ETH/BTC'})
if raises:
with pytest.warns(FutureWarning):
# Test for Future warning
# FutureWarning: Setting an item of incompatible dtype is
# deprecated and will raise in a future error of pandas
# https://github.com/pandas-dev/pandas/issues/56503
getattr(_STRATEGY, function)(df, {'pair': 'ETH/BTC'})
else:
getattr(_STRATEGY, function)(df, {'pair': 'ETH/BTC'})
def test_pandas_warning_through_analyze_pair(ohlcv_history, mocker, recwarn):
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
_STRATEGY.analyze_pair('ETH/BTC')
assert len(recwarn) == 0
+1 -1
View File
@@ -173,7 +173,7 @@ def test_download_data_options() -> None:
def test_plot_dataframe_options() -> None:
args = [
'plot-dataframe',
'-c', 'config_examples/config_bittrex.example.json',
'-c', 'tests/testdata/testconfigs/main_test_config.json',
'--indicators1', 'sma10', 'sma100',
'--indicators2', 'macd', 'fastd', 'fastk',
'--plot-limit', '30',
+2 -2
View File
@@ -15,7 +15,7 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti
process_deprecated_setting,
process_removed_setting,
process_temporary_deprecated_settings)
from freqtrade.configuration.environment_vars import flat_vars_to_nested_dict
from freqtrade.configuration.environment_vars import _flat_vars_to_nested_dict
from freqtrade.configuration.load_config import (load_config_file, load_file, load_from_files,
log_config_error_range)
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
@@ -1419,7 +1419,7 @@ def test_flat_vars_to_nested_dict(caplog):
'chat_id': '2151'
}
}
res = flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
res = _flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
assert res == expected
assert log_has("Loading variable 'FREQTRADE__EXCHANGE__SOME_SETTING'", caplog)
+87 -57
View File
@@ -438,6 +438,7 @@ def test_enter_positions_no_pairs_left(default_conf_usdt, ticker_usdt, limit_buy
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
get_fee=fee,
)
mocker.patch('freqtrade.configuration.config_validation._validate_whitelist')
default_conf_usdt['exchange']['pair_whitelist'] = whitelist
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
@@ -626,15 +627,16 @@ def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> No
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=TemporaryError)
reload_markets=MagicMock(side_effect=TemporaryError),
create_order=MagicMock(side_effect=TemporaryError),
)
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
sleep_mock = mocker.patch('time.sleep')
worker = Worker(args=None, config=default_conf_usdt)
patch_get_signal(worker.freqtrade)
worker._process_running()
assert sleep_mock.has_calls()
assert sleep_mock.called is True
def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) -> None:
@@ -857,7 +859,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
open_order['id'] = '22'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
assert freqtrade.execute_entry(pair, stake_amount)
assert enter_rate_mock.call_count == 1
assert enter_rate_mock.call_count == 2
assert enter_mm.call_count == 1
call_args = enter_mm.call_args_list[0][1]
assert call_args['pair'] == pair
@@ -879,7 +881,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
fix_price = 0.06
assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
# Make sure get_rate wasn't called again
assert enter_rate_mock.call_count == 0
assert enter_rate_mock.call_count == 1
assert enter_mm.call_count == 2
call_args = enter_mm.call_args_list[1][1]
@@ -1160,9 +1162,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = None
assert trade.is_short == is_short
assert trade.is_open
assert trade.stoploss_order_id is None
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
@@ -1170,34 +1172,21 @@ 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.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'})
hanging_stoploss_order = MagicMock(return_value={'id': '13434334', '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 == "102"
hanging_stoploss_order.assert_called_once_with('13434334', trade.pair)
assert trade.stoploss_order_id == "13434334"
# 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.stoploss_order_id = "102"
canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
canceled_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'canceled'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock()
amount_before = trade.amount
@@ -1213,25 +1202,14 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# should unset stoploss_order_id and return true
# as a trade actually happened
caplog.clear()
freqtrade.enter_positions()
stop_order_dict.update({'id': "104"})
stop_order_dict.update({'id': "103_1"})
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = "104"
trade.orders.append(Order(
ft_order_side='stoploss',
order_id='104',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=0.0,
))
assert trade
stoploss_order_hit = MagicMock(return_value={
'id': "104",
'id': "103_1",
'status': 'closed',
'type': 'stop_loss_limit',
'price': 3,
@@ -1273,7 +1251,40 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 0
# Seventh case: emergency exit triggered
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, is_short,
limit_order) -> None:
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)
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()
assert trade.is_short == is_short
assert trade.is_open
assert trade.stoploss_order_id is None
# emergency exit triggered
# Trailing stop should not act anymore
stoploss_order_cancelled = MagicMock(side_effect=[{
'id': "107",
@@ -1287,7 +1298,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
'info': {'stopPrice': 22},
}])
trade.stoploss_order_id = "107"
trade.is_open = True
trade.stoploss_last_update = dt_now() - timedelta(hours=1)
trade.stop_loss = 24
trade.exit_reason = None
@@ -1548,7 +1558,7 @@ def test_create_stoploss_order_invalid_order(
# Rpc is sending first buy, then sell
assert rpc_mock.call_count == 2
assert rpc_mock.call_args_list[0][0][0]['sell_reason'] == ExitType.EMERGENCY_EXIT.value
assert rpc_mock.call_args_list[0][0][0]['exit_reason'] == ExitType.EMERGENCY_EXIT.value
assert rpc_mock.call_args_list[0][0][0]['order_type'] == 'market'
assert rpc_mock.call_args_list[0][0][0]['type'] == 'exit'
assert rpc_mock.call_args_list[1][0][0]['type'] == 'exit_fill'
@@ -1606,12 +1616,15 @@ def test_create_stoploss_order_insufficient_funds(
])
@pytest.mark.usefixtures("init_persistence")
def test_handle_stoploss_on_exchange_trailing(
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price,
time_machine,
) -> None:
# 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, 'status': 'open'})
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
start_dt = dt_now()
time_machine.move_to(start_dt, tick=False)
patch_RPCManager(mocker)
mocker.patch.multiple(
EXMS,
@@ -1685,6 +1698,8 @@ def test_handle_stoploss_on_exchange_trailing(
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == '13434334'
# price jumped 2x
mocker.patch(
f'{EXMS}.fetch_ticker',
@@ -1706,16 +1721,15 @@ def test_handle_stoploss_on_exchange_trailing(
cancel_order_mock.assert_not_called()
stoploss_order_mock.assert_not_called()
# Move time by 10s ... so stoploss order should be replaced.
time_machine.move_to(start_dt + timedelta(minutes=10), tick=False)
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('13434334', 'ETH/USDT')
stoploss_order_mock.assert_called_once_with(
amount=30,
pair='ETH/USDT',
@@ -2228,6 +2242,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
order = limit_order[entry_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.0)
@@ -2297,6 +2312,7 @@ def test_update_trade_state_withorderdict(
order_id = "oid_123456"
order['id'] = order_id
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
# fetch_order should not be called!!
mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError))
patch_exchange(mocker)
@@ -2340,6 +2356,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit
order = limit_order[entry_side(is_short)]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter')
# TODO: should not be magicmock
trade = MagicMock()
@@ -3486,7 +3503,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bittrex'],
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'kraken', 'bybit'],
indirect=['limit_buy_order_canceled_empty'])
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, fee,
limit_buy_order_canceled_empty) -> None:
@@ -3767,9 +3784,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'profit_amount': 0.29554455 if is_short else 5.685,
'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'quote_currency': 'USDT',
'fiat_currency': 'USD',
'base_currency': 'ETH',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY,
'close_date': ANY,
@@ -3777,6 +3794,8 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'sub_trade': False,
'cumulative_profit': 0.0,
'stake_amount': pytest.approx(60),
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -3832,9 +3851,9 @@ 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',
'quote_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
@@ -3842,6 +3861,8 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'sub_trade': False,
'cumulative_profit': 0.0,
'stake_amount': pytest.approx(60),
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -3918,9 +3939,9 @@ def test_execute_trade_exit_custom_exit_price(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'quote_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': 'foo',
'exit_reason': 'foo',
'open_date': ANY,
'close_date': ANY,
@@ -3928,6 +3949,8 @@ def test_execute_trade_exit_custom_exit_price(
'sub_trade': False,
'cumulative_profit': 0.0,
'stake_amount': pytest.approx(60),
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -3991,9 +4014,9 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'profit_amount': -0.3 if is_short else -0.8985,
'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
'quote_currency': 'USDT',
'fiat_currency': 'USD',
'base_currency': 'ETH',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
@@ -4001,6 +4024,8 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'sub_trade': False,
'cumulative_profit': 0.0,
'stake_amount': pytest.approx(60),
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -4257,9 +4282,9 @@ def test_execute_trade_exit_market_order(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
'quote_currency': 'USDT',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY,
'close_date': ANY,
@@ -4267,7 +4292,8 @@ def test_execute_trade_exit_market_order(
'sub_trade': False,
'cumulative_profit': 0.0,
'stake_amount': pytest.approx(60),
'is_final_exit': False,
'final_profit_ratio': None,
} == last_msg
@@ -6699,11 +6725,15 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca
)
create_mock_trades(fee)
caplog.set_level(logging.DEBUG)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=10)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(10, 'aaaa'))
freqtrade.process_open_trade_positions()
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
assert freqtrade.strategy.adjust_trade_position.call_count == 1
caplog.clear()
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-10)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-10, 'partial_exit_c'))
freqtrade.process_open_trade_positions()
assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog)
assert freqtrade.strategy.adjust_trade_position.call_count == 1
trade = Trade.get_trades(trade_filter=[Trade.id == 5]).first()
assert trade.orders[-1].ft_order_tag == 'partial_exit_c'
+25 -11
View File
@@ -11,8 +11,7 @@ from freqtrade.rpc.rpc import RPC
from tests.conftest import EXMS, get_patched_freqtradebot, log_has_re, patch_get_signal
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
limit_buy_order, mocker) -> None:
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, mocker) -> None:
"""
Tests workflow of selling stoploss_on_exchange.
Sells
@@ -537,7 +536,7 @@ def test_dca_order_adjust_entry_replace_fails(
# Create DCA order for 2nd trade (so we have 2 open orders on 2 trades)
# this 2nd order won't fill.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=20)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF'))
freqtrade.process()
@@ -628,12 +627,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert log_has_re(
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES'))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell'
assert trade.orders[-1].ft_order_tag == 'PES'
assert pytest.approx(trade.stake_amount) == 40.198
assert pytest.approx(trade.amount) == 20.099 * leverage
assert trade.open_rate == 2.0
@@ -650,28 +650,42 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
caplog.clear()
# Sell more than what we got (we got ~20 coins left)
# First adjusts the amount to 20 - then rejects.
# Doesn't exit, as the amount is too high.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50)
freqtrade.process()
assert log_has_re("Adjusting amount to trade.amount as it is higher.*", caplog)
assert log_has_re("Remaining amount of 0.0 would be smaller than the minimum of 10.", caplog)
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
# Amount too low...
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-(trade.stake_amount * 0.99))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
# Amount exactly comes out as exactly 0
freqtrade.strategy.adjust_trade_position = MagicMock(
return_value=-(trade.amount / trade.leverage * 2.02))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
assert trade.is_open
assert trade.is_open is False
# use amount that would trunc to 0.0 once selling
mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1))
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
assert trade.is_open
assert trade.is_open is False
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
expected_profit = starting_amount - 40.1980 + trade.realized_profit
expected_profit = starting_amount - 60 + trade.realized_profit
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
+7 -7
View File
@@ -63,9 +63,9 @@ def test_set_loggers_syslog():
setup_logging_pre()
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) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
assert [x for x in logger.handlers if isinstance(x, logging.handlers.SysLogHandler)]
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
assert len(logger.handlers) == 3
@@ -86,9 +86,9 @@ def test_set_loggers_Filehandler(tmp_path):
setup_logging_pre()
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) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
assert [x for x in logger.handlers if isinstance(x, logging.handlers.RotatingFileHandler)]
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
assert len(logger.handlers) == 3
@@ -112,7 +112,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) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
# reset handlers to not break pytest
logger.handlers = orig_handlers
+10 -10
View File
@@ -67,12 +67,12 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit):
main(args)
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
assert log_has('Fatal exception!', caplog)
@@ -85,12 +85,12 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit):
main(args)
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
assert log_has('SIGINT received, aborting ...', caplog)
@@ -106,12 +106,12 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config_examples/config_bittrex.example.json']
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit):
main(args)
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
assert log_has('Oh snap!', caplog)
@@ -160,13 +160,13 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None:
args = Arguments([
'trade',
'-c',
'config_examples/config_bittrex.example.json'
'tests/testdata/testconfigs/main_test_config.json'
]).get_parsed_arg()
worker = Worker(args=args, config=default_conf)
with pytest.raises(SystemExit):
main(['trade', '-c', 'config_examples/config_bittrex.example.json'])
main(['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json'])
assert log_has('Using config: config_examples/config_bittrex.example.json ...', caplog)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog)
assert worker_mock.call_count == 4
assert reconfigure_mock.call_count == 1
assert isinstance(worker.freqtrade, FreqtradeBot)
@@ -187,7 +187,7 @@ def test_reconfigure(mocker, default_conf) -> None:
args = Arguments([
'trade',
'-c',
'config_examples/config_bittrex.example.json'
'tests/testdata/testconfigs/main_test_config.json'
]).get_parsed_arg()
worker = Worker(args=args, config=default_conf)
freqtrade = worker.freqtrade
+3 -27
View File
@@ -7,36 +7,12 @@ from unittest.mock import MagicMock
import pandas as pd
import pytest
from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dicts, file_dump_json,
file_load_json, is_file_in_dir, json_to_dataframe, pair_to_filename,
parse_db_uri_for_logging, plural, round_coin_value, safe_value_fallback,
from freqtrade.misc import (dataframe_to_json, deep_merge_dicts, file_dump_json, file_load_json,
is_file_in_dir, json_to_dataframe, pair_to_filename,
parse_db_uri_for_logging, plural, safe_value_fallback,
safe_value_fallback2)
def test_decimals_per_coin():
assert decimals_per_coin('USDT') == 3
assert decimals_per_coin('EUR') == 3
assert decimals_per_coin('BTC') == 8
assert decimals_per_coin('ETH') == 5
def test_round_coin_value():
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
assert round_coin_value(222.2, 'USDT') == '222.2 USDT'
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
assert round_coin_value(222.2, 'USDT', False) == '222.2'
assert round_coin_value(222.00, 'USDT', False) == '222'
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
def test_file_dump_json(mocker) -> None:
file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock())
json_dump = mocker.patch('rapidjson.dump', MagicMock())
+2 -2
View File
@@ -377,7 +377,7 @@ def test_start_plot_dataframe(mocker):
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
args = [
"plot-dataframe",
"--config", "config_examples/config_bittrex.example.json",
"--config", "tests/testdata/testconfigs/main_test_config.json",
"--pairs", "ETH/BTC"
]
start_plot_dataframe(get_args(args))
@@ -420,7 +420,7 @@ def test_start_plot_profit(mocker):
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
args = [
"plot-profit",
"--config", "config_examples/config_bittrex.example.json",
"--config", "tests/testdata/testconfigs/main_test_config.json",
"--pairs", "ETH/BTC"
]
start_plot_profit(get_args(args))
+77
View File
@@ -0,0 +1,77 @@
{
"max_open_trades": 3,
"stake_currency": "BTC",
"stake_amount": 0.05,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"timeframe": "5m",
"dry_run": true,
"cancel_open_orders_on_exit": false,
"unfilledtimeout": {
"entry": 10,
"exit": 10,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
"ETC/BTC",
"RVN/BTC",
"CRO/BTC",
"XLM/BTC",
"XRP/BTC",
"TRX/BTC",
"ADA/BTC",
"DOT/BTC"
],
"pair_blacklist": [
"DOGE/BTC"
]
},
"pairlists": [
{"method": "StaticPairList"}
],
"telegram": {
"enabled": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "freqtrader",
"password": "SuperSecurePassword"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
@@ -1,11 +1,10 @@
import shutil
import pytest
from freqtrade.persistence import Trade
from freqtrade.util.binance_mig import migrate_binance_futures_data, migrate_binance_futures_names
from freqtrade.util.migrations import (migrate_binance_futures_data, migrate_binance_futures_names,
migrate_data)
from tests.conftest import create_mock_trades_usdt, log_has
@@ -55,3 +54,13 @@ def test_binance_mig_db_conversion(default_conf_usdt, fee, caplog):
default_conf_usdt['trading_mode'] = 'futures'
migrate_binance_futures_names(default_conf_usdt)
assert log_has('Migrating binance futures pairs in database.', caplog)
def test_migration_wrapper(default_conf_usdt, mocker):
default_conf_usdt['trading_mode'] = 'futures'
binmock = mocker.patch('freqtrade.util.migrations.migrate_binance_futures_data')
funding_mock = mocker.patch('freqtrade.util.migrations.migrate_funding_fee_timeframe')
migrate_data(default_conf_usdt)
assert binmock.call_count == 1
assert funding_mock.call_count == 1
+37
View File
@@ -0,0 +1,37 @@
from freqtrade.util import decimals_per_coin, fmt_coin, round_value
def test_decimals_per_coin():
assert decimals_per_coin('USDT') == 3
assert decimals_per_coin('EUR') == 3
assert decimals_per_coin('BTC') == 8
assert decimals_per_coin('ETH') == 5
def test_fmt_coin():
assert fmt_coin(222.222222, 'USDT') == '222.222 USDT'
assert fmt_coin(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
assert fmt_coin(222.2, 'USDT') == '222.2 USDT'
assert fmt_coin(222.12745, 'EUR') == '222.127 EUR'
assert fmt_coin(0.1274512123, 'BTC') == '0.12745121 BTC'
assert fmt_coin(0.1274512123, 'ETH') == '0.12745 ETH'
assert fmt_coin(222.222222, 'USDT', False) == '222.222'
assert fmt_coin(222.2, 'USDT', False) == '222.2'
assert fmt_coin(222.00, 'USDT', False) == '222'
assert fmt_coin(222.12745, 'EUR', False) == '222.127'
assert fmt_coin(0.1274512123, 'BTC', False) == '0.12745121'
assert fmt_coin(0.1274512123, 'ETH', False) == '0.12745'
assert fmt_coin(222.2, 'USDT', False, True) == '222.200'
def test_round_value():
assert round_value(222.222222, 3) == '222.222'
assert round_value(222.2, 3) == '222.2'
assert round_value(222.00, 3) == '222'
assert round_value(222.12745, 3) == '222.127'
assert round_value(0.1274512123, 8) == '0.12745121'
assert round_value(0.1274512123, 5) == '0.12745'
assert round_value(222.2, 3, True) == '222.200'
assert round_value(222.2, 0, True) == '222'
@@ -0,0 +1,29 @@
from shutil import copytree
from freqtrade.util.migrations import migrate_funding_fee_timeframe
def test_migrate_funding_rate_timeframe(default_conf_usdt, tmp_path, testdatadir):
copytree(testdatadir / 'futures', tmp_path / 'futures')
file_4h = tmp_path / 'futures' / 'XRP_USDT_USDT-4h-funding_rate.feather'
file_8h = tmp_path / 'futures' / 'XRP_USDT_USDT-8h-funding_rate.feather'
file_1h = tmp_path / 'futures' / 'XRP_USDT_USDT-1h-futures.feather'
file_8h.rename(file_4h)
assert file_1h.exists()
assert file_4h.exists()
assert not file_8h.exists()
default_conf_usdt['datadir'] = tmp_path
# Inactive on spot trading ...
migrate_funding_fee_timeframe(default_conf_usdt, None)
default_conf_usdt['trading_mode'] = 'futures'
migrate_funding_fee_timeframe(default_conf_usdt, None)
assert not file_4h.exists()
assert file_8h.exists()
# futures files is untouched.
assert file_1h.exists()