Merge branch 'develop' into freqai_feature_engineering_functions
This commit is contained in:
@@ -1529,7 +1529,7 @@ def test_backtesting_show(mocker, testdatadir, capsys):
|
||||
args = [
|
||||
"backtesting-show",
|
||||
"--export-filename",
|
||||
f"{testdatadir / 'backtest_results/backtest-result_new.json'}",
|
||||
f"{testdatadir / 'backtest_results/backtest-result.json'}",
|
||||
"--show-pair-list"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
|
@@ -12,9 +12,11 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelis
|
||||
get_latest_hyperopt_file, load_backtest_data,
|
||||
load_backtest_metadata, load_trades, load_trades_from_db)
|
||||
from freqtrade.data.history import load_data, load_pair_history
|
||||
from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change,
|
||||
calculate_max_drawdown, calculate_underwater,
|
||||
combine_dataframes_with_mean, create_cum_profit)
|
||||
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
|
||||
calculate_expectancy, calculate_market_change,
|
||||
calculate_max_drawdown, calculate_sharpe, calculate_sortino,
|
||||
calculate_underwater, combine_dataframes_with_mean,
|
||||
create_cum_profit)
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
|
||||
from tests.conftest_trades import MOCK_TRADE_COUNT
|
||||
@@ -30,10 +32,10 @@ def test_get_latest_backtest_filename(testdatadir, mocker):
|
||||
|
||||
testdir_bt = testdatadir / "backtest_results"
|
||||
res = get_latest_backtest_filename(testdir_bt)
|
||||
assert res == 'backtest-result_new.json'
|
||||
assert res == 'backtest-result.json'
|
||||
|
||||
res = get_latest_backtest_filename(str(testdir_bt))
|
||||
assert res == 'backtest-result_new.json'
|
||||
assert res == 'backtest-result.json'
|
||||
|
||||
mocker.patch("freqtrade.data.btanalysis.json_load", return_value={})
|
||||
|
||||
@@ -81,7 +83,7 @@ def test_load_backtest_data_old_format(testdatadir, mocker):
|
||||
|
||||
def test_load_backtest_data_new_format(testdatadir):
|
||||
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
assert isinstance(bt_data, DataFrame)
|
||||
assert set(bt_data.columns) == set(BT_DATA_COLUMNS)
|
||||
@@ -182,7 +184,7 @@ def test_extract_trades_of_period(testdatadir):
|
||||
|
||||
|
||||
def test_analyze_trade_parallelism(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
res = analyze_trade_parallelism(bt_data, "5m")
|
||||
@@ -256,7 +258,7 @@ def test_combine_dataframes_with_mean_no_data(testdatadir):
|
||||
|
||||
|
||||
def test_create_cum_profit(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||
|
||||
@@ -268,11 +270,11 @@ def test_create_cum_profit(testdatadir):
|
||||
"cum_profits", timeframe="5m")
|
||||
assert "cum_profits" in cum_profits.columns
|
||||
assert cum_profits.iloc[0]['cum_profits'] == 0
|
||||
assert pytest.approx(cum_profits.iloc[-1]['cum_profits']) == 8.723007518796964e-06
|
||||
assert pytest.approx(cum_profits.iloc[-1]['cum_profits']) == 9.0225563e-05
|
||||
|
||||
|
||||
def test_create_cum_profit1(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
# Move close-time to "off" the candle, to make sure the logic still works
|
||||
bt_data['close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20)
|
||||
@@ -286,7 +288,7 @@ def test_create_cum_profit1(testdatadir):
|
||||
"cum_profits", timeframe="5m")
|
||||
assert "cum_profits" in cum_profits.columns
|
||||
assert cum_profits.iloc[0]['cum_profits'] == 0
|
||||
assert pytest.approx(cum_profits.iloc[-1]['cum_profits']) == 8.723007518796964e-06
|
||||
assert pytest.approx(cum_profits.iloc[-1]['cum_profits']) == 9.0225563e-05
|
||||
|
||||
with pytest.raises(ValueError, match='Trade dataframe empty.'):
|
||||
create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'NOTAPAIR'],
|
||||
@@ -294,18 +296,18 @@ def test_create_cum_profit1(testdatadir):
|
||||
|
||||
|
||||
def test_calculate_max_drawdown(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
_, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown(
|
||||
bt_data, value_col="profit_abs")
|
||||
assert isinstance(drawdown, float)
|
||||
assert pytest.approx(drawdown) == 0.12071099
|
||||
assert pytest.approx(drawdown) == 0.29753914
|
||||
assert isinstance(hdate, Timestamp)
|
||||
assert isinstance(lowdate, Timestamp)
|
||||
assert isinstance(hval, float)
|
||||
assert isinstance(lval, float)
|
||||
assert hdate == Timestamp('2018-01-25 01:30:00', tz='UTC')
|
||||
assert lowdate == Timestamp('2018-01-25 03:50:00', tz='UTC')
|
||||
assert hdate == Timestamp('2018-01-16 19:30:00', tz='UTC')
|
||||
assert lowdate == Timestamp('2018-01-16 22:25:00', tz='UTC')
|
||||
|
||||
underwater = calculate_underwater(bt_data)
|
||||
assert isinstance(underwater, DataFrame)
|
||||
@@ -318,14 +320,15 @@ def test_calculate_max_drawdown(testdatadir):
|
||||
|
||||
|
||||
def test_calculate_csum(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
csum_min, csum_max = calculate_csum(bt_data)
|
||||
|
||||
assert isinstance(csum_min, float)
|
||||
assert isinstance(csum_max, float)
|
||||
assert csum_min < 0.01
|
||||
assert csum_max > 0.02
|
||||
assert csum_min < csum_max
|
||||
assert csum_min < 0.0001
|
||||
assert csum_max > 0.0002
|
||||
csum_min1, csum_max1 = calculate_csum(bt_data, 5)
|
||||
|
||||
assert csum_min1 == csum_min + 5
|
||||
@@ -335,6 +338,69 @@ def test_calculate_csum(testdatadir):
|
||||
csum_min, csum_max = calculate_csum(DataFrame())
|
||||
|
||||
|
||||
def test_calculate_expectancy(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
expectancy = calculate_expectancy(DataFrame())
|
||||
assert expectancy == 0.0
|
||||
|
||||
expectancy = calculate_expectancy(bt_data)
|
||||
assert isinstance(expectancy, float)
|
||||
assert pytest.approx(expectancy) == 0.07151374226574791
|
||||
|
||||
|
||||
def test_calculate_sortino(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
sortino = calculate_sortino(DataFrame(), None, None, 0)
|
||||
assert sortino == 0.0
|
||||
|
||||
sortino = calculate_sortino(
|
||||
bt_data,
|
||||
bt_data['open_date'].min(),
|
||||
bt_data['close_date'].max(),
|
||||
0.01,
|
||||
)
|
||||
assert isinstance(sortino, float)
|
||||
assert pytest.approx(sortino) == 35.17722
|
||||
|
||||
|
||||
def test_calculate_sharpe(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
sharpe = calculate_sharpe(DataFrame(), None, None, 0)
|
||||
assert sharpe == 0.0
|
||||
|
||||
sharpe = calculate_sharpe(
|
||||
bt_data,
|
||||
bt_data['open_date'].min(),
|
||||
bt_data['close_date'].max(),
|
||||
0.01,
|
||||
)
|
||||
assert isinstance(sharpe, float)
|
||||
assert pytest.approx(sharpe) == 44.5078669
|
||||
|
||||
|
||||
def test_calculate_calmar(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
|
||||
calmar = calculate_calmar(DataFrame(), None, None, 0)
|
||||
assert calmar == 0.0
|
||||
|
||||
calmar = calculate_calmar(
|
||||
bt_data,
|
||||
bt_data['open_date'].min(),
|
||||
bt_data['close_date'].max(),
|
||||
0.01,
|
||||
)
|
||||
assert isinstance(calmar, float)
|
||||
assert pytest.approx(calmar) == 559.040508
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,days, expected', [
|
||||
(64900, 176000, 3 * 365, 0.3945),
|
||||
(64900, 176000, 365, 1.7119),
|
||||
|
@@ -23,7 +23,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'limit'
|
||||
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
|
||||
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
|
@@ -8,16 +8,19 @@ suitable to run with freqtrade.
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import CandleType
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
|
||||
from freqtrade.exchange.exchange import timeframe_to_msecs
|
||||
from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_default_conf_usdt
|
||||
|
||||
|
||||
EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str]
|
||||
|
||||
# Exchanges that should be tested
|
||||
EXCHANGES = {
|
||||
'bittrex': {
|
||||
@@ -141,19 +144,19 @@ def exchange_futures(request, exchange_conf, class_mocker):
|
||||
@pytest.mark.longrun
|
||||
class TestCCXTExchange():
|
||||
|
||||
def test_load_markets(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_load_markets(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
markets = exchange.markets
|
||||
markets = exch.markets
|
||||
assert pair in markets
|
||||
assert isinstance(markets[pair], dict)
|
||||
assert exchange.market_is_spot(markets[pair])
|
||||
assert exch.market_is_spot(markets[pair])
|
||||
|
||||
def test_has_validations(self, exchange):
|
||||
def test_has_validations(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
|
||||
exchange, exchangename = exchange
|
||||
exch, exchangename = exchange
|
||||
|
||||
exchange.validate_ordertypes({
|
||||
exch.validate_ordertypes({
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'limit',
|
||||
@@ -162,13 +165,13 @@ class TestCCXTExchange():
|
||||
if exchangename == 'gateio':
|
||||
# gateio doesn't have market orders on spot
|
||||
return
|
||||
exchange.validate_ordertypes({
|
||||
exch.validate_ordertypes({
|
||||
'entry': 'market',
|
||||
'exit': 'market',
|
||||
'stoploss': 'market',
|
||||
})
|
||||
|
||||
def test_load_markets_futures(self, exchange_futures):
|
||||
def test_load_markets_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
@@ -181,11 +184,11 @@ class TestCCXTExchange():
|
||||
|
||||
assert exchange.market_is_future(markets[pair])
|
||||
|
||||
def test_ccxt_fetch_tickers(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt_fetch_tickers(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
|
||||
tickers = exchange.get_tickers()
|
||||
tickers = exch.get_tickers()
|
||||
assert pair in tickers
|
||||
assert 'ask' in tickers[pair]
|
||||
assert tickers[pair]['ask'] is not None
|
||||
@@ -195,11 +198,11 @@ class TestCCXTExchange():
|
||||
if EXCHANGES[exchangename].get('hasQuoteVolume'):
|
||||
assert tickers[pair]['quoteVolume'] is not None
|
||||
|
||||
def test_ccxt_fetch_ticker(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt_fetch_ticker(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
|
||||
ticker = exchange.fetch_ticker(pair)
|
||||
ticker = exch.fetch_ticker(pair)
|
||||
assert 'ask' in ticker
|
||||
assert ticker['ask'] is not None
|
||||
assert 'bid' in ticker
|
||||
@@ -208,21 +211,21 @@ class TestCCXTExchange():
|
||||
if EXCHANGES[exchangename].get('hasQuoteVolume'):
|
||||
assert ticker['quoteVolume'] is not None
|
||||
|
||||
def test_ccxt_fetch_l2_orderbook(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt_fetch_l2_orderbook(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
l2 = exchange.fetch_l2_order_book(pair)
|
||||
l2 = exch.fetch_l2_order_book(pair)
|
||||
assert 'asks' in l2
|
||||
assert 'bids' in l2
|
||||
assert len(l2['asks']) >= 1
|
||||
assert len(l2['bids']) >= 1
|
||||
l2_limit_range = exchange._ft_has['l2_limit_range']
|
||||
l2_limit_range_required = exchange._ft_has['l2_limit_range_required']
|
||||
l2_limit_range = exch._ft_has['l2_limit_range']
|
||||
l2_limit_range_required = exch._ft_has['l2_limit_range_required']
|
||||
if exchangename == 'gateio':
|
||||
# TODO: Gateio is unstable here at the moment, ignoring the limit partially.
|
||||
return
|
||||
for val in [1, 2, 5, 25, 100]:
|
||||
l2 = exchange.fetch_l2_order_book(pair, val)
|
||||
l2 = exch.fetch_l2_order_book(pair, val)
|
||||
if not l2_limit_range or val in l2_limit_range:
|
||||
if val > 50:
|
||||
# Orderbooks are not always this deep.
|
||||
@@ -232,7 +235,7 @@ class TestCCXTExchange():
|
||||
assert len(l2['asks']) == val
|
||||
assert len(l2['bids']) == val
|
||||
else:
|
||||
next_limit = exchange.get_next_limit_in_list(
|
||||
next_limit = exch.get_next_limit_in_list(
|
||||
val, l2_limit_range, l2_limit_range_required)
|
||||
if next_limit is None:
|
||||
assert len(l2['asks']) > 100
|
||||
@@ -245,23 +248,23 @@ class TestCCXTExchange():
|
||||
assert len(l2['asks']) == next_limit
|
||||
assert len(l2['asks']) == next_limit
|
||||
|
||||
def test_ccxt_fetch_ohlcv(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt_fetch_ohlcv(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
timeframe = EXCHANGES[exchangename]['timeframe']
|
||||
|
||||
pair_tf = (pair, timeframe, CandleType.SPOT)
|
||||
|
||||
ohlcv = exchange.refresh_latest_ohlcv([pair_tf])
|
||||
ohlcv = exch.refresh_latest_ohlcv([pair_tf])
|
||||
assert isinstance(ohlcv, dict)
|
||||
assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf))
|
||||
# assert len(exchange.klines(pair_tf)) > 200
|
||||
assert len(ohlcv[pair_tf]) == len(exch.klines(pair_tf))
|
||||
# assert len(exch.klines(pair_tf)) > 200
|
||||
# Assume 90% uptime ...
|
||||
assert len(exchange.klines(pair_tf)) > exchange.ohlcv_candle_limit(
|
||||
assert len(exch.klines(pair_tf)) > exch.ohlcv_candle_limit(
|
||||
timeframe, CandleType.SPOT) * 0.90
|
||||
# Check if last-timeframe is within the last 2 intervals
|
||||
now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2))
|
||||
assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
|
||||
assert exch.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
|
||||
|
||||
def ccxt__async_get_candle_history(self, exchange, exchangename, pair, timeframe, candle_type):
|
||||
|
||||
@@ -289,17 +292,17 @@ class TestCCXTExchange():
|
||||
assert len(candles) >= min(candle_count, candle_count1)
|
||||
assert candles[0][0] == since_ms or (since_ms + timeframe_ms)
|
||||
|
||||
def test_ccxt__async_get_candle_history(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exc, exchangename = exchange
|
||||
# For some weired reason, this test returns random lengths for bittrex.
|
||||
if not exchange._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'):
|
||||
if not exc._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'):
|
||||
return
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
timeframe = EXCHANGES[exchangename]['timeframe']
|
||||
self.ccxt__async_get_candle_history(
|
||||
exchange, exchangename, pair, timeframe, CandleType.SPOT)
|
||||
exc, exchangename, pair, timeframe, CandleType.SPOT)
|
||||
|
||||
def test_ccxt__async_get_candle_history_futures(self, exchange_futures):
|
||||
def test_ccxt__async_get_candle_history_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
@@ -309,7 +312,7 @@ class TestCCXTExchange():
|
||||
self.ccxt__async_get_candle_history(
|
||||
exchange, exchangename, pair, timeframe, CandleType.FUTURES)
|
||||
|
||||
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
|
||||
def test_ccxt_fetch_funding_rate_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
@@ -347,7 +350,7 @@ class TestCCXTExchange():
|
||||
(rate['open'].min() != rate['open'].max())
|
||||
)
|
||||
|
||||
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
|
||||
def test_ccxt_fetch_mark_price_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
@@ -371,7 +374,7 @@ class TestCCXTExchange():
|
||||
assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0
|
||||
assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0
|
||||
|
||||
def test_ccxt__calculate_funding_fees(self, exchange_futures):
|
||||
def test_ccxt__calculate_funding_fees(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
@@ -387,16 +390,16 @@ class TestCCXTExchange():
|
||||
|
||||
# TODO: tests fetch_trades (?)
|
||||
|
||||
def test_ccxt_get_fee(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
def test_ccxt_get_fee(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
exch, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
threshold = 0.01
|
||||
assert 0 < exchange.get_fee(pair, 'limit', 'buy') < threshold
|
||||
assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold
|
||||
assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold
|
||||
assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold
|
||||
assert 0 < exch.get_fee(pair, 'limit', 'buy') < threshold
|
||||
assert 0 < exch.get_fee(pair, 'limit', 'sell') < threshold
|
||||
assert 0 < exch.get_fee(pair, 'market', 'buy') < threshold
|
||||
assert 0 < exch.get_fee(pair, 'market', 'sell') < threshold
|
||||
|
||||
def test_ccxt_get_max_leverage_spot(self, exchange):
|
||||
def test_ccxt_get_max_leverage_spot(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
spot, spot_name = exchange
|
||||
if spot:
|
||||
leverage_in_market_spot = EXCHANGES[spot_name].get('leverage_in_spot_market')
|
||||
@@ -406,7 +409,7 @@ class TestCCXTExchange():
|
||||
assert (isinstance(spot_leverage, float) or isinstance(spot_leverage, int))
|
||||
assert spot_leverage >= 1.0
|
||||
|
||||
def test_ccxt_get_max_leverage_futures(self, exchange_futures):
|
||||
def test_ccxt_get_max_leverage_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
futures, futures_name = exchange_futures
|
||||
if futures:
|
||||
leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public')
|
||||
@@ -419,7 +422,7 @@ class TestCCXTExchange():
|
||||
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
|
||||
assert futures_leverage >= 1.0
|
||||
|
||||
def test_ccxt_get_contract_size(self, exchange_futures):
|
||||
def test_ccxt_get_contract_size(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
futures, futures_name = exchange_futures
|
||||
if futures:
|
||||
futures_pair = EXCHANGES[futures_name].get(
|
||||
@@ -430,7 +433,7 @@ class TestCCXTExchange():
|
||||
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
|
||||
assert contract_size >= 0.0
|
||||
|
||||
def test_ccxt_load_leverage_tiers(self, exchange_futures):
|
||||
def test_ccxt_load_leverage_tiers(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
futures, futures_name = exchange_futures
|
||||
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
|
||||
leverage_tiers = futures.load_leverage_tiers()
|
||||
@@ -463,7 +466,7 @@ class TestCCXTExchange():
|
||||
oldminNotional = tier['minNotional']
|
||||
oldmaxNotional = tier['maxNotional']
|
||||
|
||||
def test_ccxt_dry_run_liquidation_price(self, exchange_futures):
|
||||
def test_ccxt_dry_run_liquidation_price(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
futures, futures_name = exchange_futures
|
||||
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
|
||||
|
||||
@@ -494,7 +497,7 @@ class TestCCXTExchange():
|
||||
assert (isinstance(liquidation_price, float))
|
||||
assert liquidation_price >= 0.0
|
||||
|
||||
def test_ccxt_get_max_pair_stake_amount(self, exchange_futures):
|
||||
def test_ccxt_get_max_pair_stake_amount(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
|
||||
futures, futures_name = exchange_futures
|
||||
if futures:
|
||||
futures_pair = EXCHANGES[futures_name].get(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.leverage import interest
|
||||
from freqtrade.util import FtPrecise
|
||||
|
||||
@@ -29,3 +30,13 @@ def test_interest(exchange, interest_rate, hours, expected):
|
||||
rate=FtPrecise(interest_rate),
|
||||
hours=hours
|
||||
))) == expected
|
||||
|
||||
|
||||
def test_interest_exception():
|
||||
with pytest.raises(OperationalException, match=r"Leverage not available on .* with freqtrade"):
|
||||
interest(
|
||||
exchange_name='bitmex',
|
||||
borrowed=FtPrecise(60.0),
|
||||
rate=FtPrecise(0.0005),
|
||||
hours=ten_mins
|
||||
)
|
||||
|
@@ -710,6 +710,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
expected = pd.DataFrame(
|
||||
{'pair': [pair, pair],
|
||||
'stake_amount': [0.001, 0.001],
|
||||
'max_stake_amount': [0.001, 0.001],
|
||||
'amount': [0.00957442, 0.0097064],
|
||||
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
|
||||
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
|
||||
|
@@ -50,6 +50,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
|
||||
expected = pd.DataFrame(
|
||||
{'pair': [pair, pair],
|
||||
'stake_amount': [500.0, 100.0],
|
||||
'max_stake_amount': [500.0, 100],
|
||||
'amount': [4806.87657523, 970.63960782],
|
||||
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
|
||||
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
|
||||
|
@@ -308,7 +308,7 @@ def test_generate_pair_metrics():
|
||||
|
||||
def test_generate_daily_stats(testdatadir):
|
||||
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
res = generate_daily_stats(bt_data)
|
||||
assert isinstance(res, dict)
|
||||
@@ -328,7 +328,7 @@ def test_generate_daily_stats(testdatadir):
|
||||
|
||||
|
||||
def test_generate_trading_stats(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
res = generate_trading_stats(bt_data)
|
||||
assert isinstance(res, dict)
|
||||
@@ -444,7 +444,7 @@ def test_generate_edge_table():
|
||||
|
||||
|
||||
def test_generate_periodic_breakdown_stats(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename).to_dict(orient='records')
|
||||
|
||||
res = generate_periodic_breakdown_stats(bt_data, 'day')
|
||||
@@ -472,7 +472,7 @@ def test__get_resample_from_period():
|
||||
|
||||
|
||||
def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_stats(filename)
|
||||
default_conf['backtest_show_pair_list'] = True
|
||||
|
||||
|
412
tests/persistence/test_migrations.py
Normal file
412
tests/persistence/test_migrations.py
Normal file
@@ -0,0 +1,412 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from freqtrade.constants import DEFAULT_DB_PROD_URL
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
|
||||
from freqtrade.persistence.models import PairLock
|
||||
from tests.conftest import log_has
|
||||
|
||||
|
||||
spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
# Check if init create a session
|
||||
init_db(default_conf['db_url'])
|
||||
assert hasattr(Trade, '_session')
|
||||
assert 'scoped_session' in type(Trade._session).__name__
|
||||
|
||||
|
||||
def test_init_custom_db_url(default_conf, tmpdir):
|
||||
# Update path to a value other than default, but still in-memory
|
||||
filename = f"{tmpdir}/freqtrade2_test.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
|
||||
default_conf.update({'db_url': f'sqlite:///{filename}'})
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert Path(filename).is_file()
|
||||
r = Trade._session.execute(text("PRAGMA journal_mode"))
|
||||
assert r.first() == ('wal',)
|
||||
|
||||
|
||||
def test_init_invalid_db_url():
|
||||
# Update path to a value other than default, but still in-memory
|
||||
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
||||
init_db('unknown:///some.url')
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'):
|
||||
init_db('sqlite:///')
|
||||
|
||||
|
||||
def test_init_prod_db(default_conf, mocker):
|
||||
default_conf.update({'dry_run': False})
|
||||
default_conf.update({'db_url': DEFAULT_DB_PROD_URL})
|
||||
|
||||
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert create_engine_mock.call_count == 1
|
||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
|
||||
|
||||
|
||||
def test_init_dryrun_db(default_conf, tmpdir):
|
||||
filename = f"{tmpdir}/freqtrade2_prod.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
default_conf.update({
|
||||
'dry_run': True,
|
||||
'db_url': f'sqlite:///{filename}'
|
||||
})
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert Path(filename).is_file()
|
||||
|
||||
|
||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
amount = 103.223
|
||||
# Always create all columns apart from the last!
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
stop_loss FLOAT,
|
||||
initial_stop_loss FLOAT,
|
||||
max_rate FLOAT,
|
||||
sell_reason VARCHAR,
|
||||
strategy VARCHAR,
|
||||
ticker_interval INTEGER,
|
||||
stoploss_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
create_table_order = """CREATE TABLE orders (
|
||||
id INTEGER NOT NULL,
|
||||
ft_trade_id INTEGER,
|
||||
ft_order_side VARCHAR(25) NOT NULL,
|
||||
ft_pair VARCHAR(25) NOT NULL,
|
||||
ft_is_open BOOLEAN NOT NULL,
|
||||
order_id VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(255),
|
||||
symbol VARCHAR(25),
|
||||
order_type VARCHAR(50),
|
||||
side VARCHAR(25),
|
||||
price FLOAT,
|
||||
amount FLOAT,
|
||||
filled FLOAT,
|
||||
remaining FLOAT,
|
||||
cost FLOAT,
|
||||
order_date DATETIME,
|
||||
order_filled_date DATETIME,
|
||||
order_update_date DATETIME,
|
||||
PRIMARY KEY (id)
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, stake_amount, amount, open_date,
|
||||
stop_loss, initial_stop_loss, max_rate, ticker_interval,
|
||||
open_order_id, stoploss_order_id)
|
||||
VALUES ('binance', 'ETC/BTC', 1, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000',
|
||||
0.0, 0.0, 0.0, '5m',
|
||||
'buy_order', 'dry_stop_order_id222')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
insert_orders = f"""
|
||||
insert into orders (
|
||||
ft_trade_id,
|
||||
ft_order_side,
|
||||
ft_pair,
|
||||
ft_is_open,
|
||||
order_id,
|
||||
status,
|
||||
symbol,
|
||||
order_type,
|
||||
side,
|
||||
price,
|
||||
amount,
|
||||
filled,
|
||||
remaining,
|
||||
cost)
|
||||
values (
|
||||
1,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
0,
|
||||
'dry_buy_order',
|
||||
'closed',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_buy_order22',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id11X',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id222',
|
||||
'open',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
)
|
||||
"""
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(create_table_order))
|
||||
connection.execute(text("create index ix_trades_is_open on trades(is_open)"))
|
||||
connection.execute(text("create index ix_trades_pair on trades(pair)"))
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_orders))
|
||||
|
||||
# fake previous backup
|
||||
connection.execute(text("create table trades_bak as select * from trades"))
|
||||
|
||||
connection.execute(text("create table trades_bak1 as select * from trades"))
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.is_open == 1
|
||||
assert trade.amount == amount
|
||||
assert trade.amount_requested == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.max_rate == 0.0
|
||||
assert trade.min_rate is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.exit_reason is None
|
||||
assert trade.strategy is None
|
||||
assert trade.timeframe == '5m'
|
||||
assert trade.stoploss_order_id == 'dry_stop_order_id222'
|
||||
assert trade.stoploss_last_update is None
|
||||
assert log_has("trying trades_bak1", caplog)
|
||||
assert log_has("trying trades_bak2", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||
caplog)
|
||||
assert log_has("Database migration finished.", caplog)
|
||||
assert pytest.approx(trade.open_trade_value) == trade._calc_open_trade_value(
|
||||
trade.amount, trade.open_rate)
|
||||
assert trade.close_profit_abs is None
|
||||
assert trade.stake_amount == trade.max_stake_amount
|
||||
|
||||
orders = trade.orders
|
||||
assert len(orders) == 4
|
||||
assert orders[0].order_id == 'dry_buy_order'
|
||||
assert orders[0].ft_order_side == 'buy'
|
||||
|
||||
assert orders[-1].order_id == 'dry_stop_order_id222'
|
||||
assert orders[-1].ft_order_side == 'stoploss'
|
||||
assert orders[-1].ft_is_open is True
|
||||
|
||||
assert orders[1].order_id == 'dry_buy_order22'
|
||||
assert orders[1].ft_order_side == 'buy'
|
||||
assert orders[1].ft_is_open is False
|
||||
|
||||
assert orders[2].order_id == 'dry_stop_order_id11X'
|
||||
assert orders[2].ft_order_side == 'stoploss'
|
||||
assert orders[2].ft_is_open is False
|
||||
|
||||
|
||||
def test_migrate_too_old(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
amount = 103.223
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee_open FLOAT NOT NULL,
|
||||
fee_close FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('binance', 'ETC/BTC', 1, {fee}, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(insert_table_old))
|
||||
|
||||
# Run init to test migration
|
||||
with pytest.raises(OperationalException, match=r'Your database seems to be very old'):
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
|
||||
def test_migrate_get_last_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 2
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_migrate_set_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
set_sequence_ids(engine, 22, 55, 5)
|
||||
|
||||
assert engine.begin.call_count == 1
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
set_sequence_ids(engine, 22, 55, 6)
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
# Always create all columns apart from the last!
|
||||
create_table_old = """CREATE TABLE pairlocks (
|
||||
id INTEGER NOT NULL,
|
||||
pair VARCHAR(25) NOT NULL,
|
||||
reason VARCHAR(255),
|
||||
lock_time DATETIME NOT NULL,
|
||||
lock_end_time DATETIME NOT NULL,
|
||||
active BOOLEAN NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
"""
|
||||
create_index1 = "CREATE INDEX ix_pairlocks_pair ON pairlocks (pair)"
|
||||
create_index2 = "CREATE INDEX ix_pairlocks_lock_end_time ON pairlocks (lock_end_time)"
|
||||
create_index3 = "CREATE INDEX ix_pairlocks_active ON pairlocks (active)"
|
||||
insert_table_old = """INSERT INTO pairlocks (
|
||||
id, pair, reason, lock_time, lock_end_time, active)
|
||||
VALUES (1, 'ETH/BTC', 'Auto lock', '2021-07-12 18:41:03', '2021-07-11 18:45:00', 1)
|
||||
"""
|
||||
insert_table_old2 = """INSERT INTO pairlocks (
|
||||
id, pair, reason, lock_time, lock_end_time, active)
|
||||
VALUES (2, '*', 'Lock all', '2021-07-12 18:41:03', '2021-07-12 19:00:00', 1)
|
||||
"""
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_table_old2))
|
||||
connection.execute(text(create_index1))
|
||||
connection.execute(text(create_index2))
|
||||
connection.execute(text(create_index3))
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(PairLock.query.all()) == 2
|
||||
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1
|
||||
pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()
|
||||
assert len(pairlocks) == 1
|
||||
pairlocks[0].pair == 'ETH/BTC'
|
||||
pairlocks[0].side == '*'
|
@@ -1,78 +1,20 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from types import FunctionType
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_DB_PROD_URL
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
|
||||
from freqtrade.persistence.models import PairLock
|
||||
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
|
||||
|
||||
|
||||
spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
# Check if init create a session
|
||||
init_db(default_conf['db_url'])
|
||||
assert hasattr(Trade, '_session')
|
||||
assert 'scoped_session' in type(Trade._session).__name__
|
||||
|
||||
|
||||
def test_init_custom_db_url(default_conf, tmpdir):
|
||||
# Update path to a value other than default, but still in-memory
|
||||
filename = f"{tmpdir}/freqtrade2_test.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
|
||||
default_conf.update({'db_url': f'sqlite:///{filename}'})
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert Path(filename).is_file()
|
||||
r = Trade._session.execute(text("PRAGMA journal_mode"))
|
||||
assert r.first() == ('wal',)
|
||||
|
||||
|
||||
def test_init_invalid_db_url():
|
||||
# Update path to a value other than default, but still in-memory
|
||||
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
||||
init_db('unknown:///some.url')
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'):
|
||||
init_db('sqlite:///')
|
||||
|
||||
|
||||
def test_init_prod_db(default_conf, mocker):
|
||||
default_conf.update({'dry_run': False})
|
||||
default_conf.update({'db_url': DEFAULT_DB_PROD_URL})
|
||||
|
||||
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert create_engine_mock.call_count == 1
|
||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
|
||||
|
||||
|
||||
def test_init_dryrun_db(default_conf, tmpdir):
|
||||
filename = f"{tmpdir}/freqtrade2_prod.sqlite"
|
||||
assert not Path(filename).is_file()
|
||||
default_conf.update({
|
||||
'dry_run': True,
|
||||
'db_url': f'sqlite:///{filename}'
|
||||
})
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert Path(filename).is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short', [False, True])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_enter_exit_side(fee, is_short):
|
||||
@@ -316,8 +258,7 @@ def test_interest(fee, exchange, is_short, lev, minutes, rate, interest,
|
||||
(True, 3.0, 30.0, margin),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee,
|
||||
caplog, is_short, lev, borrowed, trading_mode):
|
||||
def test_borrowed(fee, is_short, lev, borrowed, trading_mode):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
fee: 0.25% quote
|
||||
@@ -1204,347 +1145,6 @@ def test_calc_profit(
|
||||
trade.open_rate)) == round(profit_ratio, 8)
|
||||
|
||||
|
||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
amount = 103.223
|
||||
# Always create all columns apart from the last!
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
stop_loss FLOAT,
|
||||
initial_stop_loss FLOAT,
|
||||
max_rate FLOAT,
|
||||
sell_reason VARCHAR,
|
||||
strategy VARCHAR,
|
||||
ticker_interval INTEGER,
|
||||
stoploss_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
create_table_order = """CREATE TABLE orders (
|
||||
id INTEGER NOT NULL,
|
||||
ft_trade_id INTEGER,
|
||||
ft_order_side VARCHAR(25) NOT NULL,
|
||||
ft_pair VARCHAR(25) NOT NULL,
|
||||
ft_is_open BOOLEAN NOT NULL,
|
||||
order_id VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(255),
|
||||
symbol VARCHAR(25),
|
||||
order_type VARCHAR(50),
|
||||
side VARCHAR(25),
|
||||
price FLOAT,
|
||||
amount FLOAT,
|
||||
filled FLOAT,
|
||||
remaining FLOAT,
|
||||
cost FLOAT,
|
||||
order_date DATETIME,
|
||||
order_filled_date DATETIME,
|
||||
order_update_date DATETIME,
|
||||
PRIMARY KEY (id)
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, stake_amount, amount, open_date,
|
||||
stop_loss, initial_stop_loss, max_rate, ticker_interval,
|
||||
open_order_id, stoploss_order_id)
|
||||
VALUES ('binance', 'ETC/BTC', 1, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000',
|
||||
0.0, 0.0, 0.0, '5m',
|
||||
'buy_order', 'dry_stop_order_id222')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
insert_orders = f"""
|
||||
insert into orders (
|
||||
ft_trade_id,
|
||||
ft_order_side,
|
||||
ft_pair,
|
||||
ft_is_open,
|
||||
order_id,
|
||||
status,
|
||||
symbol,
|
||||
order_type,
|
||||
side,
|
||||
price,
|
||||
amount,
|
||||
filled,
|
||||
remaining,
|
||||
cost)
|
||||
values (
|
||||
1,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
0,
|
||||
'dry_buy_order',
|
||||
'closed',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_buy_order22',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id11X',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id222',
|
||||
'open',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
)
|
||||
"""
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(create_table_order))
|
||||
connection.execute(text("create index ix_trades_is_open on trades(is_open)"))
|
||||
connection.execute(text("create index ix_trades_pair on trades(pair)"))
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_orders))
|
||||
|
||||
# fake previous backup
|
||||
connection.execute(text("create table trades_bak as select * from trades"))
|
||||
|
||||
connection.execute(text("create table trades_bak1 as select * from trades"))
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.is_open == 1
|
||||
assert trade.amount == amount
|
||||
assert trade.amount_requested == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.max_rate == 0.0
|
||||
assert trade.min_rate is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.exit_reason is None
|
||||
assert trade.strategy is None
|
||||
assert trade.timeframe == '5m'
|
||||
assert trade.stoploss_order_id == 'dry_stop_order_id222'
|
||||
assert trade.stoploss_last_update is None
|
||||
assert log_has("trying trades_bak1", caplog)
|
||||
assert log_has("trying trades_bak2", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||
caplog)
|
||||
assert log_has("Database migration finished.", caplog)
|
||||
assert pytest.approx(trade.open_trade_value) == trade._calc_open_trade_value(
|
||||
trade.amount, trade.open_rate)
|
||||
assert trade.close_profit_abs is None
|
||||
|
||||
orders = trade.orders
|
||||
assert len(orders) == 4
|
||||
assert orders[0].order_id == 'dry_buy_order'
|
||||
assert orders[0].ft_order_side == 'buy'
|
||||
|
||||
assert orders[-1].order_id == 'dry_stop_order_id222'
|
||||
assert orders[-1].ft_order_side == 'stoploss'
|
||||
assert orders[-1].ft_is_open is True
|
||||
|
||||
assert orders[1].order_id == 'dry_buy_order22'
|
||||
assert orders[1].ft_order_side == 'buy'
|
||||
assert orders[1].ft_is_open is False
|
||||
|
||||
assert orders[2].order_id == 'dry_stop_order_id11X'
|
||||
assert orders[2].ft_order_side == 'stoploss'
|
||||
assert orders[2].ft_is_open is False
|
||||
|
||||
|
||||
def test_migrate_too_old(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
amount = 103.223
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee_open FLOAT NOT NULL,
|
||||
fee_close FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('binance', 'ETC/BTC', 1, {fee}, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(insert_table_old))
|
||||
|
||||
# Run init to test migration
|
||||
with pytest.raises(OperationalException, match=r'Your database seems to be very old'):
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
|
||||
def test_migrate_get_last_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 2
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
get_last_sequence_ids(engine, 'trades_bak', 'orders_bak')
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_migrate_set_sequence_ids():
|
||||
engine = MagicMock()
|
||||
engine.begin = MagicMock()
|
||||
engine.name = 'postgresql'
|
||||
set_sequence_ids(engine, 22, 55, 5)
|
||||
|
||||
assert engine.begin.call_count == 1
|
||||
engine.reset_mock()
|
||||
engine.begin.reset_mock()
|
||||
|
||||
engine.name = 'somethingelse'
|
||||
set_sequence_ids(engine, 22, 55, 6)
|
||||
|
||||
assert engine.begin.call_count == 0
|
||||
|
||||
|
||||
def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
# Always create all columns apart from the last!
|
||||
create_table_old = """CREATE TABLE pairlocks (
|
||||
id INTEGER NOT NULL,
|
||||
pair VARCHAR(25) NOT NULL,
|
||||
reason VARCHAR(255),
|
||||
lock_time DATETIME NOT NULL,
|
||||
lock_end_time DATETIME NOT NULL,
|
||||
active BOOLEAN NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
"""
|
||||
create_index1 = "CREATE INDEX ix_pairlocks_pair ON pairlocks (pair)"
|
||||
create_index2 = "CREATE INDEX ix_pairlocks_lock_end_time ON pairlocks (lock_end_time)"
|
||||
create_index3 = "CREATE INDEX ix_pairlocks_active ON pairlocks (active)"
|
||||
insert_table_old = """INSERT INTO pairlocks (
|
||||
id, pair, reason, lock_time, lock_end_time, active)
|
||||
VALUES (1, 'ETH/BTC', 'Auto lock', '2021-07-12 18:41:03', '2021-07-11 18:45:00', 1)
|
||||
"""
|
||||
insert_table_old2 = """INSERT INTO pairlocks (
|
||||
id, pair, reason, lock_time, lock_end_time, active)
|
||||
VALUES (2, '*', 'Lock all', '2021-07-12 18:41:03', '2021-07-12 19:00:00', 1)
|
||||
"""
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_table_old2))
|
||||
connection.execute(text(create_index1))
|
||||
connection.execute(text(create_index2))
|
||||
connection.execute(text(create_index3))
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(PairLock.query.all()) == 2
|
||||
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1
|
||||
pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()
|
||||
assert len(pairlocks) == 1
|
||||
pairlocks[0].pair == 'ETH/BTC'
|
||||
pairlocks[0].side == '*'
|
||||
|
||||
|
||||
def test_adjust_stop_loss(fee):
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
@@ -1758,6 +1358,7 @@ def test_to_json(fee):
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
@@ -1767,7 +1368,6 @@ def test_to_json(fee):
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'sell_reason': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'stop_loss_abs': None,
|
||||
@@ -1782,7 +1382,6 @@ def test_to_json(fee):
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'buy_tag': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
@@ -1826,6 +1425,7 @@ def test_to_json(fee):
|
||||
'amount': 100.0,
|
||||
'amount_requested': 101.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': 60,
|
||||
'trade_duration_s': 3600,
|
||||
'stop_loss_abs': None,
|
||||
@@ -1857,11 +1457,9 @@ def test_to_json(fee):
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'sell_reason': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': None,
|
||||
'buy_tag': 'buys_signal_001',
|
||||
'enter_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
|
@@ -46,13 +46,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_value': 0.0010025,
|
||||
'close_rate_requested': ANY,
|
||||
'sell_reason': ANY,
|
||||
'exit_reason': ANY,
|
||||
'exit_order_status': ANY,
|
||||
'min_rate': ANY,
|
||||
'max_rate': ANY,
|
||||
'strategy': ANY,
|
||||
'buy_tag': ANY,
|
||||
'enter_tag': ANY,
|
||||
'timeframe': 5,
|
||||
'open_order_id': ANY,
|
||||
@@ -64,6 +62,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'amount': 91.07468123,
|
||||
'amount_requested': 91.07468124,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': ANY,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'close_profit': None,
|
||||
|
@@ -985,6 +985,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'base_currency': 'ETH',
|
||||
'quote_currency': 'BTC',
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': ANY,
|
||||
'stop_loss_abs': ANY,
|
||||
'stop_loss_pct': ANY,
|
||||
'stop_loss_ratio': ANY,
|
||||
@@ -1014,11 +1015,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'open_order_id': open_order_id,
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_value': open_trade_value,
|
||||
'sell_reason': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'buy_tag': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': 5,
|
||||
'exchange': 'binance',
|
||||
@@ -1188,6 +1187,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'base_currency': 'ETH',
|
||||
'quote_currency': 'BTC',
|
||||
'stake_amount': 1,
|
||||
'max_stake_amount': ANY,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
@@ -1218,11 +1218,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'open_order_id': '123456',
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 0.24605460,
|
||||
'sell_reason': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'buy_tag': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': 5,
|
||||
'exchange': 'binance',
|
||||
@@ -1709,7 +1707,7 @@ def test_api_backtest_history(botclient, mocker, testdatadir):
|
||||
mocker.patch('freqtrade.data.btanalysis._get_backtest_files',
|
||||
return_value=[
|
||||
testdatadir / 'backtest_results/backtest-result_multistrat.json',
|
||||
testdatadir / 'backtest_results/backtest-result_new.json'
|
||||
testdatadir / 'backtest_results/backtest-result.json'
|
||||
])
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/backtest/history")
|
||||
|
@@ -46,7 +46,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir):
|
||||
default_conf['trade_source'] = "file"
|
||||
default_conf['timeframe'] = "5m"
|
||||
default_conf["datadir"] = testdatadir
|
||||
default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
|
||||
default_conf['exportfilename'] = testdatadir / "backtest-result.json"
|
||||
supported_markets = ["TRX/BTC", "ADA/BTC"]
|
||||
ret = init_plotscript(default_conf, supported_markets)
|
||||
assert "ohlcv" in ret
|
||||
@@ -158,7 +158,7 @@ def test_plot_trades(testdatadir, caplog):
|
||||
assert fig == fig1
|
||||
assert log_has("No trades found.", caplog)
|
||||
pair = "ADA/BTC"
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
trades = load_backtest_data(filename)
|
||||
trades = trades.loc[trades['pair'] == pair]
|
||||
|
||||
@@ -299,7 +299,7 @@ def test_generate_plot_file(mocker, caplog):
|
||||
|
||||
|
||||
def test_add_profit(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
bt_data = load_backtest_data(filename)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||
|
||||
@@ -319,7 +319,7 @@ def test_add_profit(testdatadir):
|
||||
|
||||
|
||||
def test_generate_profit_graph(testdatadir):
|
||||
filename = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
trades = load_backtest_data(filename)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||
pairs = ["TRX/BTC", "XLM/BTC"]
|
||||
@@ -354,7 +354,7 @@ def test_generate_profit_graph(testdatadir):
|
||||
|
||||
profit = find_trace_in_fig_data(figure.data, "Profit")
|
||||
assert isinstance(profit, go.Scatter)
|
||||
drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 35.69%")
|
||||
drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 73.89%")
|
||||
assert isinstance(drawdown, go.Scatter)
|
||||
parallel = find_trace_in_fig_data(figure.data, "Parallel trades")
|
||||
assert isinstance(parallel, go.Scatter)
|
||||
@@ -395,7 +395,7 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
|
||||
|
||||
default_conf['trade_source'] = 'file'
|
||||
default_conf["datadir"] = testdatadir
|
||||
default_conf['exportfilename'] = testdatadir / "backtest-result_new.json"
|
||||
default_conf['exportfilename'] = testdatadir / "backtest-result.json"
|
||||
default_conf['indicators1'] = ["sma5", "ema10"]
|
||||
default_conf['indicators2'] = ["macd"]
|
||||
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
|
||||
@@ -466,7 +466,7 @@ def test_plot_profit(default_conf, mocker, testdatadir):
|
||||
match=r"No trades found, cannot generate Profit-plot.*"):
|
||||
plot_profit(default_conf)
|
||||
|
||||
default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result_new.json"
|
||||
default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result.json"
|
||||
|
||||
plot_profit(default_conf)
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
{"latest_backtest":"backtest-result_new.json"}
|
||||
{"latest_backtest":"backtest-result.json"}
|
||||
|
1
tests/testdata/backtest_results/backtest-result.json
vendored
Normal file
1
tests/testdata/backtest_results/backtest-result.json
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user