Merge branch 'develop' into feature/fetch-public-trades
This commit is contained in:
@@ -12,9 +12,9 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star
|
||||
start_create_userdir, start_download_data, start_hyperopt_list,
|
||||
start_hyperopt_show, start_install_ui, start_list_data,
|
||||
start_list_exchanges, start_list_markets, start_list_strategies,
|
||||
start_list_timeframes, start_new_strategy, start_show_trades,
|
||||
start_strategy_update, start_test_pairlist, start_trading,
|
||||
start_webserver)
|
||||
start_list_timeframes, start_new_strategy, start_show_config,
|
||||
start_show_trades, start_strategy_update, start_test_pairlist,
|
||||
start_trading, start_webserver)
|
||||
from freqtrade.commands.db_commands import start_convert_db
|
||||
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
|
||||
get_ui_download_url, read_ui_version)
|
||||
@@ -39,6 +39,14 @@ def test_setup_utils_configuration():
|
||||
assert "exchange" in config
|
||||
assert config['dry_run'] is True
|
||||
|
||||
args = [
|
||||
'list-exchanges', '--config', 'tests/testdata/testconfigs/testconfig.json',
|
||||
]
|
||||
|
||||
config = setup_utils_configuration(get_args(args), RunMode.OTHER, set_dry=False)
|
||||
assert "exchange" in config
|
||||
assert config['dry_run'] is False
|
||||
|
||||
|
||||
def test_start_trading_fail(mocker, caplog):
|
||||
|
||||
@@ -51,15 +59,16 @@ def test_start_trading_fail(mocker, caplog):
|
||||
'trade',
|
||||
'-c', 'tests/testdata/testconfigs/main_test_config.json'
|
||||
]
|
||||
start_trading(get_args(args))
|
||||
with pytest.raises(OperationalException):
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 1
|
||||
|
||||
exitmock.reset_mock()
|
||||
caplog.clear()
|
||||
mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(side_effect=OperationalException))
|
||||
start_trading(get_args(args))
|
||||
with pytest.raises(OperationalException):
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 0
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
|
||||
|
||||
def test_start_webserver(mocker, caplog):
|
||||
@@ -1571,3 +1580,33 @@ def test_start_strategy_updater(mocker, tmp_path):
|
||||
start_strategy_update(pargs)
|
||||
# Number of strategies in the test directory
|
||||
assert sc_mock.call_count == 2
|
||||
|
||||
|
||||
def test_start_show_config(capsys, caplog):
|
||||
args = [
|
||||
"show-config",
|
||||
"--config",
|
||||
"tests/testdata/testconfigs/main_test_config.json",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
start_show_config(pargs)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Your combined configuration is:" in captured.out
|
||||
assert '"max_open_trades":' in captured.out
|
||||
assert '"secret": "REDACTED"' in captured.out
|
||||
|
||||
args = [
|
||||
"show-config",
|
||||
"--config",
|
||||
"tests/testdata/testconfigs/main_test_config.json",
|
||||
"--show-sensitive"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
start_show_config(pargs)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Your combined configuration is:" in captured.out
|
||||
assert '"max_open_trades":' in captured.out
|
||||
assert '"secret": "REDACTED"' not in captured.out
|
||||
assert log_has_re(r'Sensitive information will be shown in the upcomming output.*', caplog)
|
||||
|
||||
@@ -11,8 +11,8 @@ from numpy import NaN
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||
InsufficientFundsError, InvalidOrderException,
|
||||
from freqtrade.exceptions import (ConfigurationError, DDosProtection, DependencyException,
|
||||
ExchangeError, InsufficientFundsError, InvalidOrderException,
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exchange import (Binance, Bybit, Exchange, Kraken, market_is_active,
|
||||
timeframe_to_prev_date)
|
||||
@@ -595,7 +595,7 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||
mocker.patch(f'{EXMS}.validate_pairs')
|
||||
mocker.patch(f'{EXMS}.validate_timeframes')
|
||||
mocker.patch(f'{EXMS}._load_async_markets')
|
||||
with pytest.raises(OperationalException,
|
||||
with pytest.raises(ConfigurationError,
|
||||
match=r'XRP is not available as stake on .*'
|
||||
'Available currencies are: BTC, ETH, USDT'):
|
||||
Exchange(default_conf)
|
||||
@@ -800,12 +800,12 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
||||
mocker.patch(f'{EXMS}.validate_pairs')
|
||||
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||
mocker.patch(f'{EXMS}.validate_pricing')
|
||||
with pytest.raises(OperationalException,
|
||||
with pytest.raises(ConfigurationError,
|
||||
match=r"Invalid timeframe '3m'. This exchange supports.*"):
|
||||
Exchange(default_conf)
|
||||
default_conf["timeframe"] = "15s"
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
with pytest.raises(ConfigurationError,
|
||||
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
|
||||
Exchange(default_conf)
|
||||
|
||||
@@ -1066,6 +1066,9 @@ def test_exchange_has(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
assert not exchange.exchange_has("deadbeef")
|
||||
|
||||
exchange._ft_has['exchange_has_overrides'] = {'deadbeef': True}
|
||||
assert exchange.exchange_has("deadbeef")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,leverage", [
|
||||
("buy", 1),
|
||||
|
||||
@@ -262,6 +262,13 @@ EXCHANGES = {
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': False,
|
||||
},
|
||||
'bingx': {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '1h',
|
||||
'futures': False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1233,6 +1233,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
order_id=order_id,
|
||||
|
||||
))
|
||||
freqtrade.strategy.order_filled = MagicMock(return_value=None)
|
||||
assert not freqtrade.update_trade_state(trade, None)
|
||||
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
|
||||
caplog.clear()
|
||||
@@ -1243,6 +1244,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
caplog.clear()
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == order['amount']
|
||||
assert freqtrade.strategy.order_filled.call_count == 1
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.01)
|
||||
assert trade.amount == 30.0
|
||||
@@ -1260,11 +1262,13 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
limit_buy_order_usdt_new['filled'] = 0.0
|
||||
limit_buy_order_usdt_new['status'] = 'canceled'
|
||||
|
||||
freqtrade.strategy.order_filled = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError)
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=limit_buy_order_usdt_new)
|
||||
res = freqtrade.update_trade_state(trade, order_id)
|
||||
# Cancelled empty
|
||||
assert res is True
|
||||
assert freqtrade.strategy.order_filled.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -5460,9 +5464,10 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca
|
||||
assert freqtrade.strategy.adjust_trade_position.call_count == 1
|
||||
|
||||
caplog.clear()
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-10, 'partial_exit_c'))
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-0.0005, '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'
|
||||
assert trade.is_open
|
||||
|
||||
@@ -636,12 +636,12 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
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 pytest.approx(trade.stake_amount) == 40
|
||||
assert pytest.approx(trade.amount) == 20 * leverage
|
||||
assert trade.open_rate == 2.0
|
||||
assert trade.is_open
|
||||
assert trade.realized_profit > 0.098 * leverage
|
||||
expected_profit = starting_amount - 40.1980 + trade.realized_profit
|
||||
expected_profit = starting_amount - 40 + trade.realized_profit
|
||||
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
|
||||
|
||||
if spot:
|
||||
@@ -667,14 +667,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
|
||||
# Amount exactly comes out as exactly 0
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(
|
||||
return_value=-(trade.amount / trade.leverage * 2.02))
|
||||
return_value=-trade.stake_amount)
|
||||
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 pytest.approx(trade.stake_amount) == 40
|
||||
assert trade.is_open is False
|
||||
|
||||
# use amount that would trunc to 0.0 once selling
|
||||
@@ -684,7 +684,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
|
||||
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 pytest.approx(trade.stake_amount) == 40
|
||||
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 - 60 + trade.realized_profit
|
||||
|
||||
@@ -146,10 +146,12 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
'amount': enter_order['amount'],
|
||||
})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
|
||||
freqtrade.strategy.order_filled = MagicMock(return_value=None)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
|
||||
assert len(trade.open_sl_orders) == 0
|
||||
assert trade.is_open is False
|
||||
assert freqtrade.strategy.order_filled.call_count == 1
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError())
|
||||
|
||||
@@ -698,6 +698,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
backtesting.strategy.order_filled = MagicMock()
|
||||
min_date, max_date = get_timerange(processed)
|
||||
|
||||
result = backtesting.backtest(
|
||||
@@ -760,6 +761,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
pd.testing.assert_frame_equal(results, expected)
|
||||
assert 'orders' in results.columns
|
||||
data_pair = processed[pair]
|
||||
# Called once per order
|
||||
assert backtesting.strategy.order_filled.call_count == 4
|
||||
for _, t in results.iterrows():
|
||||
assert len(t['orders']) == 2
|
||||
ln = data_pair.loc[data_pair["date"] == t["open_date"]]
|
||||
@@ -1470,7 +1473,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||
text_table_mock = MagicMock()
|
||||
sell_reason_mock = MagicMock()
|
||||
tag_metrics_mock = MagicMock()
|
||||
strattable_mock = MagicMock()
|
||||
strat_summary = MagicMock()
|
||||
|
||||
@@ -1480,7 +1483,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
)
|
||||
mocker.patch.multiple('freqtrade.optimize.optimize_reports.optimize_reports',
|
||||
generate_pair_metrics=MagicMock(),
|
||||
generate_exit_reason_stats=sell_reason_mock,
|
||||
generate_tag_metrics=tag_metrics_mock,
|
||||
generate_strategy_comparison=strat_summary,
|
||||
generate_daily_stats=MagicMock(),
|
||||
)
|
||||
@@ -1505,7 +1508,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
assert backtestmock.call_count == 2
|
||||
assert text_table_mock.call_count == 4
|
||||
assert strattable_mock.call_count == 1
|
||||
assert sell_reason_mock.call_count == 2
|
||||
assert tag_metrics_mock.call_count == 4
|
||||
assert strat_summary.call_count == 1
|
||||
|
||||
# check the logs, that will contain the backtest result
|
||||
|
||||
@@ -15,16 +15,16 @@ from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backte
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
|
||||
generate_edge_table, generate_exit_reason_stats,
|
||||
generate_pair_metrics,
|
||||
generate_edge_table, generate_pair_metrics,
|
||||
generate_periodic_breakdown_stats,
|
||||
generate_strategy_comparison,
|
||||
generate_trading_stats, show_sorted_pairlist,
|
||||
store_backtest_analysis_results,
|
||||
store_backtest_stats, text_table_bt_results,
|
||||
text_table_exit_reason, text_table_strategy)
|
||||
text_table_strategy)
|
||||
from freqtrade.optimize.optimize_reports.bt_output import text_table_tags
|
||||
from freqtrade.optimize.optimize_reports.optimize_reports import (_get_resample_from_period,
|
||||
calc_streak)
|
||||
calc_streak, generate_tag_metrics)
|
||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver
|
||||
from freqtrade.util import dt_ts
|
||||
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
|
||||
@@ -59,13 +59,13 @@ def test_text_table_bt_results():
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | '
|
||||
'| Pair | Entries | Avg Profit % | Tot Profit BTC | '
|
||||
'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n'
|
||||
'|---------+-----------+----------------+----------------+------------------+'
|
||||
'|---------+-----------+----------------+------------------+'
|
||||
'----------------+----------------+-------------------------|\n'
|
||||
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||
'| ETH/BTC | 3 | 8.33 | 0.50000000 | '
|
||||
'12.50 | 0:20:00 | 2 0 1 66.7 |\n'
|
||||
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||
'| TOTAL | 3 | 8.33 | 0.50000000 | '
|
||||
'12.50 | 0:20:00 | 2 0 1 66.7 |'
|
||||
)
|
||||
|
||||
@@ -392,20 +392,21 @@ def test_text_table_exit_reason():
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Exit Reason | Exits | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
|
||||
' Tot Profit BTC | Tot Profit % |\n'
|
||||
'|---------------+---------+--------------------------+----------------+----------------+'
|
||||
'------------------+----------------|\n'
|
||||
'| roi | 2 | 2 0 0 100 | 15 | 30 |'
|
||||
' 0.6 | 15 |\n'
|
||||
'| stop_loss | 1 | 0 0 1 0 | -10 | -10 |'
|
||||
' -0.2 | -5 |'
|
||||
'| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |'
|
||||
' Avg Duration | Win Draw Loss Win% |\n'
|
||||
'|---------------+---------+----------------+------------------+----------------+'
|
||||
'----------------+-------------------------|\n'
|
||||
'| roi | 2 | 15.00 | 0.60000000 | 2.73 |'
|
||||
' 0:20:00 | 2 0 0 100 |\n'
|
||||
'| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |'
|
||||
' 0:10:00 | 0 0 1 0 |\n'
|
||||
'| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |'
|
||||
' 0:17:00 | 2 0 1 66.7 |'
|
||||
)
|
||||
|
||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
|
||||
results=results)
|
||||
assert text_table_exit_reason(exit_reason_stats=exit_reason_stats,
|
||||
stake_currency='BTC') == result_str
|
||||
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
|
||||
results=results, skip_nan=False)
|
||||
assert text_table_tags('exit_tag', exit_reason_stats, 'BTC') == result_str
|
||||
|
||||
|
||||
def test_generate_sell_reason_stats():
|
||||
@@ -423,10 +424,10 @@ def test_generate_sell_reason_stats():
|
||||
}
|
||||
)
|
||||
|
||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
|
||||
results=results)
|
||||
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
|
||||
results=results, skip_nan=False)
|
||||
roi_result = exit_reason_stats[0]
|
||||
assert roi_result['exit_reason'] == 'roi'
|
||||
assert roi_result['key'] == 'roi'
|
||||
assert roi_result['trades'] == 2
|
||||
assert pytest.approx(roi_result['profit_mean']) == 0.15
|
||||
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
|
||||
@@ -435,7 +436,7 @@ def test_generate_sell_reason_stats():
|
||||
|
||||
stop_result = exit_reason_stats[1]
|
||||
|
||||
assert stop_result['exit_reason'] == 'stop_loss'
|
||||
assert stop_result['key'] == 'stop_loss'
|
||||
assert stop_result['trades'] == 1
|
||||
assert pytest.approx(stop_result['profit_mean']) == -0.1
|
||||
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
|
||||
@@ -450,13 +451,13 @@ def test_text_table_strategy(testdatadir):
|
||||
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
|
||||
|
||||
result_str = (
|
||||
'| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
||||
'| Strategy | Entries | Avg Profit % | Tot Profit BTC |'
|
||||
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
|
||||
'|----------------+-----------+----------------+----------------+------------------+'
|
||||
'|----------------+-----------+----------------+------------------+'
|
||||
'----------------+----------------+-------------------------+-----------------------|\n'
|
||||
'| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |'
|
||||
'| StrategyTestV2 | 179 | 0.08 | 0.02608550 |'
|
||||
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n'
|
||||
'| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |'
|
||||
'| TestStrategy | 179 | 0.08 | 0.02608550 |'
|
||||
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |'
|
||||
)
|
||||
|
||||
|
||||
@@ -406,6 +406,14 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
([{"method": "VolumePairList", "number_assets": 5,
|
||||
"sort_key": "quoteVolume", "min_value": 1250}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
|
||||
# HOT, XRP and FUEL whitelisted because they are below 1300 quoteVolume.
|
||||
([{"method": "VolumePairList", "number_assets": 5,
|
||||
"sort_key": "quoteVolume", "max_value": 1300}],
|
||||
"BTC", ['XRP/BTC', 'HOT/BTC', 'FUEL/BTC']),
|
||||
# HOT, XRP whitelisted because they are between 100 and 1300 quoteVolume.
|
||||
([{"method": "VolumePairList", "number_assets": 5,
|
||||
"sort_key": "quoteVolume", "min_value": 100, "max_value": 1300}],
|
||||
"BTC", ['XRP/BTC', 'HOT/BTC']),
|
||||
# StaticPairlist only
|
||||
([{"method": "StaticPairList"}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
|
||||
|
||||
+65
-65
@@ -11,7 +11,6 @@ from freqtrade.enums import SignalDirection, State, TradingMode
|
||||
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||
from freqtrade.persistence import Order, Trade
|
||||
from freqtrade.persistence.key_value_store import set_startup_time
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
from freqtrade.rpc import RPC, RPCException
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt,
|
||||
@@ -491,12 +490,12 @@ def test_rpc_balance_handle_error(default_conf, mocker):
|
||||
rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
|
||||
|
||||
|
||||
def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'free': 10.0,
|
||||
'total': 12.0,
|
||||
'used': 2.0,
|
||||
'free': 0.01,
|
||||
'total': 0.012,
|
||||
'used': 0.002,
|
||||
},
|
||||
'ETH': {
|
||||
'free': 1.0,
|
||||
@@ -504,8 +503,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
'used': 4.0,
|
||||
},
|
||||
'USDT': {
|
||||
'free': 5.0,
|
||||
'total': 10.0,
|
||||
'free': 50.0,
|
||||
'total': 100.0,
|
||||
'used': 5.0,
|
||||
}
|
||||
}
|
||||
@@ -519,10 +518,10 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
"maintenanceMargin": 0.0,
|
||||
"maintenanceMarginPercentage": 0.005,
|
||||
"entryPrice": 0.0,
|
||||
"notional": 100.0,
|
||||
"notional": 10.0,
|
||||
"leverage": 5.0,
|
||||
"unrealizedPnl": 0.0,
|
||||
"contracts": 100.0,
|
||||
"contracts": 1.0,
|
||||
"contractSize": 1,
|
||||
"marginRatio": None,
|
||||
"liquidationPrice": 0.0,
|
||||
@@ -536,9 +535,9 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
|
||||
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
|
||||
get_price=MagicMock(return_value={'bitcoin': {'usd': 1.2}}),
|
||||
)
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.2)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
@@ -549,86 +548,86 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
get_valid_pair_combination=MagicMock(
|
||||
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
|
||||
)
|
||||
default_conf['dry_run'] = False
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
default_conf_usdt['dry_run'] = False
|
||||
default_conf_usdt['trading_mode'] = 'futures'
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
patch_get_signal(freqtradebot)
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
|
||||
assert pytest.approx(result['total']) == 30.30909624
|
||||
assert pytest.approx(result['value']) == 454636.44360691
|
||||
result = rpc._rpc_balance(
|
||||
default_conf_usdt['stake_currency'], default_conf_usdt['fiat_display_currency'])
|
||||
|
||||
assert pytest.approx(result['total']) == 2824.83464
|
||||
assert pytest.approx(result['value']) == 2824.83464 * 1.2
|
||||
assert tickers.call_count == 1
|
||||
assert tickers.call_args_list[0][1]['cached'] is True
|
||||
assert 'USD' == result['symbol']
|
||||
assert result['currencies'] == [
|
||||
{
|
||||
'currency': 'BTC',
|
||||
'free': 10.0,
|
||||
'balance': 12.0,
|
||||
'used': 2.0,
|
||||
'bot_owned': 9.9, # available stake - reducing by reserved amount
|
||||
'est_stake': 10.0, # In futures mode, "free" is used here.
|
||||
'est_stake_bot': 9.9,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'free': 0.01,
|
||||
'balance': 0.012,
|
||||
'used': 0.002,
|
||||
'bot_owned': 0,
|
||||
'est_stake': 103.78464,
|
||||
'est_stake_bot': 0,
|
||||
'stake': 'USDT',
|
||||
'side': 'long',
|
||||
'is_bot_managed': True,
|
||||
'leverage': 1,
|
||||
'position': 0,
|
||||
'is_bot_managed': False,
|
||||
'is_position': False
|
||||
},
|
||||
{
|
||||
'currency': 'ETH',
|
||||
'free': 1.0,
|
||||
'balance': 5.0,
|
||||
'currency': 'ETH',
|
||||
'bot_owned': 0,
|
||||
'est_stake': 0.30794,
|
||||
'est_stake_bot': 0,
|
||||
'used': 4.0,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'side': 'long',
|
||||
'is_bot_managed': False,
|
||||
},
|
||||
{
|
||||
'free': 5.0,
|
||||
'balance': 10.0,
|
||||
'currency': 'USDT',
|
||||
'bot_owned': 0,
|
||||
'est_stake': 0.0011562404610161968,
|
||||
'est_stake': 2651.05,
|
||||
'est_stake_bot': 0,
|
||||
'used': 5.0,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'stake': 'USDT',
|
||||
'side': 'long',
|
||||
'leverage': 1,
|
||||
'position': 0,
|
||||
'is_bot_managed': False,
|
||||
'is_position': False
|
||||
},
|
||||
{
|
||||
'currency': 'USDT',
|
||||
'free': 50.0,
|
||||
'balance': 100.0,
|
||||
'used': 5.0,
|
||||
'bot_owned': 49.5,
|
||||
'est_stake': 50.0,
|
||||
'est_stake_bot': 49.5,
|
||||
'stake': 'USDT',
|
||||
'side': 'long',
|
||||
'leverage': 1,
|
||||
'position': 0,
|
||||
'is_bot_managed': True,
|
||||
'is_position': False
|
||||
},
|
||||
{
|
||||
'free': 0.0,
|
||||
'balance': 0.0,
|
||||
'currency': 'ETH/USDT:USDT',
|
||||
'free': 0,
|
||||
'balance': 0,
|
||||
'used': 0,
|
||||
'position': 10.0,
|
||||
'est_stake': 20,
|
||||
'est_stake_bot': 20,
|
||||
'used': 0,
|
||||
'stake': 'BTC',
|
||||
'is_position': True,
|
||||
'stake': 'USDT',
|
||||
'leverage': 5.0,
|
||||
'position': 1000.0,
|
||||
'side': 'short',
|
||||
'is_bot_managed': True,
|
||||
'is_position': True
|
||||
}
|
||||
]
|
||||
assert pytest.approx(result['total_bot']) == 29.9
|
||||
assert pytest.approx(result['total']) == 30.309096
|
||||
assert result['starting_capital'] == 10
|
||||
# Very high starting capital ratio, because the futures position really has the wrong unit.
|
||||
# TODO: improve this test (see comment above)
|
||||
assert result['starting_capital_ratio'] == pytest.approx(1.98999999)
|
||||
assert pytest.approx(result['total_bot']) == 69.5
|
||||
assert pytest.approx(result['total']) == 2824.83464 # ETH stake is missing.
|
||||
assert result['starting_capital'] == 50
|
||||
assert result['starting_capital_ratio'] == pytest.approx(0.3899999)
|
||||
|
||||
|
||||
def test_rpc_start(mocker, default_conf) -> None:
|
||||
@@ -1171,14 +1170,15 @@ def test_rpc_force_entry_wrong_mode(mocker, default_conf) -> None:
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_rpc_delete_lock(mocker, default_conf):
|
||||
def test_rpc_add_and_delete_lock(mocker, default_conf):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=4))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=5))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=10))
|
||||
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=4), '', '*')
|
||||
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=5), '', '*')
|
||||
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=10), '', '*')
|
||||
|
||||
locks = rpc._rpc_locks()
|
||||
assert locks['lock_count'] == 3
|
||||
locks1 = rpc._rpc_delete_lock(lockid=locks['locks'][0]['id'])
|
||||
|
||||
@@ -23,12 +23,13 @@ from freqtrade.enums import CandleType, RunMode, State, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
|
||||
from freqtrade.loggers import setup_logging, setup_logging_pre
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.api_server import ApiServer
|
||||
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
|
||||
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
|
||||
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
|
||||
from freqtrade.util.datetime_helpers import format_date
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro,
|
||||
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
|
||||
|
||||
@@ -553,8 +554,19 @@ def test_api_locks(botclient):
|
||||
assert rc.json()['lock_count'] == 0
|
||||
assert rc.json()['lock_count'] == len(rc.json()['locks'])
|
||||
|
||||
PairLocks.lock_pair('ETH/BTC', datetime.now(timezone.utc) + timedelta(minutes=4), 'randreason')
|
||||
PairLocks.lock_pair('XRP/BTC', datetime.now(timezone.utc) + timedelta(minutes=20), 'deadbeef')
|
||||
rc = client_post(client, f"{BASE_URI}/locks", [
|
||||
{
|
||||
"pair": "ETH/BTC",
|
||||
"until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=4))}Z",
|
||||
"reason": "randreason"
|
||||
}, {
|
||||
"pair": "XRP/BTC",
|
||||
"until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=20))}Z",
|
||||
"reason": "deadbeef"
|
||||
}
|
||||
])
|
||||
assert_response(rc)
|
||||
assert rc.json()['lock_count'] == 2
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/locks")
|
||||
assert_response(rc)
|
||||
|
||||
@@ -795,9 +795,6 @@ def test_strategy_safe_wrapper_error(caplog, error):
|
||||
def failing_method():
|
||||
raise error('This is an error.')
|
||||
|
||||
def working_method(argumentpassedin):
|
||||
return argumentpassedin
|
||||
|
||||
with pytest.raises(StrategyError, match=r'This is an error.'):
|
||||
strategy_safe_wrapper(failing_method, message='DeadBeef')()
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from jsonschema import ValidationError
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.configuration import Configuration, validate_config_consistency
|
||||
from freqtrade.configuration.config_secrets import sanitize_config
|
||||
from freqtrade.configuration.config_validation import validate_config_schema
|
||||
from freqtrade.configuration.deprecated_settings import (check_conflicting_settings,
|
||||
process_deprecated_setting,
|
||||
@@ -1440,7 +1441,7 @@ def test_flat_vars_to_nested_dict(caplog):
|
||||
assert not log_has("Loading variable 'NOT_RELEVANT'", caplog)
|
||||
|
||||
|
||||
def test_setup_hyperopt_freqai(mocker, default_conf, caplog) -> None:
|
||||
def test_setup_hyperopt_freqai(mocker, default_conf) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
mocker.patch(
|
||||
'freqtrade.configuration.configuration.create_datadir',
|
||||
@@ -1473,7 +1474,7 @@ def test_setup_hyperopt_freqai(mocker, default_conf, caplog) -> None:
|
||||
validate_config_consistency(config)
|
||||
|
||||
|
||||
def test_setup_freqai_backtesting(mocker, default_conf, caplog) -> None:
|
||||
def test_setup_freqai_backtesting(mocker, default_conf) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
mocker.patch(
|
||||
'freqtrade.configuration.configuration.create_datadir',
|
||||
@@ -1520,3 +1521,17 @@ def test_setup_freqai_backtesting(mocker, default_conf, caplog) -> None:
|
||||
OperationalException, match=r".* pass --timerange if you intend to use FreqAI .*"
|
||||
):
|
||||
validate_config_consistency(conf)
|
||||
|
||||
|
||||
def test_sanitize_config(default_conf_usdt):
|
||||
assert default_conf_usdt['exchange']['key'] != 'REDACTED'
|
||||
res = sanitize_config(default_conf_usdt)
|
||||
# Didn't modify original dict
|
||||
assert default_conf_usdt['exchange']['key'] != 'REDACTED'
|
||||
|
||||
assert res['exchange']['key'] == 'REDACTED'
|
||||
assert res['exchange']['secret'] == 'REDACTED'
|
||||
|
||||
res = sanitize_config(default_conf_usdt, show_sensitive=True)
|
||||
assert res['exchange']['key'] == default_conf_usdt['exchange']['key']
|
||||
assert res['exchange']['secret'] == default_conf_usdt['exchange']['secret']
|
||||
|
||||
+17
-1
@@ -8,7 +8,7 @@ import pytest
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||
from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.main import main
|
||||
from freqtrade.worker import Worker
|
||||
@@ -141,6 +141,22 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
|
||||
assert log_has_re(r'SIGINT.*', caplog)
|
||||
|
||||
|
||||
def test_main_ConfigurationError(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
'freqtrade.commands.list_commands.list_available_exchanges',
|
||||
MagicMock(side_effect=ConfigurationError('Oh snap!'))
|
||||
)
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
args = ['list-exchanges']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
assert log_has_re('Configuration error: Oh snap!', caplog)
|
||||
|
||||
|
||||
def test_main_reload_config(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"stake_currency": "",
|
||||
"dry_run": true,
|
||||
"dry_run": false,
|
||||
"exchange": {
|
||||
"name": "",
|
||||
"key": "",
|
||||
|
||||
Reference in New Issue
Block a user