ruff format: Update a few test files

This commit is contained in:
Matthias 2024-05-12 15:29:14 +02:00
parent baa15f6ed6
commit 7090950db6
13 changed files with 1629 additions and 1283 deletions

View File

@ -32,13 +32,12 @@ def is_arm() -> bool:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def patch_torch_initlogs(mocker) -> None: def patch_torch_initlogs(mocker) -> None:
if is_mac(): if is_mac():
# Mock torch import completely # Mock torch import completely
import sys import sys
import types import types
module_name = 'torch' module_name = "torch"
mocked_module = types.ModuleType(module_name) mocked_module = types.ModuleType(module_name)
sys.modules[module_name] = mocked_module sys.modules[module_name] = mocked_module
else: else:
@ -80,25 +79,23 @@ def freqai_conf(default_conf, tmp_path):
"stratify_training_data": 0, "stratify_training_data": 0,
"indicator_periods_candles": [10], "indicator_periods_candles": [10],
"shuffle_after_split": False, "shuffle_after_split": False,
"buffer_train_data_candles": 0 "buffer_train_data_candles": 0,
}, },
"data_split_parameters": {"test_size": 0.33, "shuffle": False}, "data_split_parameters": {"test_size": 0.33, "shuffle": False},
"model_training_parameters": {"n_estimators": 100}, "model_training_parameters": {"n_estimators": 100},
}, },
"config_files": [Path('config_examples', 'config_freqai.example.json')] "config_files": [Path("config_examples", "config_freqai.example.json")],
} }
) )
freqaiconf['exchange'].update({'pair_whitelist': ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC']}) freqaiconf["exchange"].update({"pair_whitelist": ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC"]})
return freqaiconf return freqaiconf
def make_rl_config(conf): def make_rl_config(conf):
conf.update({"strategy": "freqai_rl_test_strat"}) conf.update({"strategy": "freqai_rl_test_strat"})
conf["freqai"].update({"model_training_parameters": { conf["freqai"].update(
"learning_rate": 0.00025, {"model_training_parameters": {"learning_rate": 0.00025, "gamma": 0.9, "verbose": 1}}
"gamma": 0.9, )
"verbose": 1
}})
conf["freqai"]["rl_config"] = { conf["freqai"]["rl_config"] = {
"train_cycles": 1, "train_cycles": 1,
"thread_count": 2, "thread_count": 2,
@ -107,31 +104,27 @@ def make_rl_config(conf):
"policy_type": "MlpPolicy", "policy_type": "MlpPolicy",
"max_training_drawdown_pct": 0.5, "max_training_drawdown_pct": 0.5,
"net_arch": [32, 32], "net_arch": [32, 32],
"model_reward_parameters": { "model_reward_parameters": {"rr": 1, "profit_aim": 0.02, "win_reward_factor": 2},
"rr": 1, "drop_ohlc_from_features": False,
"profit_aim": 0.02, }
"win_reward_factor": 2
},
"drop_ohlc_from_features": False
}
return conf return conf
def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]: def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
return { return {
"learning_rate": 3e-4, "learning_rate": 3e-4,
"trainer_kwargs": { "trainer_kwargs": {
"n_steps": None, "n_steps": None,
"batch_size": 64, "batch_size": 64,
"n_epochs": 1, "n_epochs": 1,
}, },
"model_kwargs": { "model_kwargs": {
"hidden_dim": 32, "hidden_dim": 32,
"dropout_percent": 0.2, "dropout_percent": 0.2,
"n_layer": 1, "n_layer": 1,
} },
} }
def get_patched_data_kitchen(mocker, freqaiconf): def get_patched_data_kitchen(mocker, freqaiconf):
@ -178,14 +171,14 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
new_timerange = TimeRange.parse_timerange("20180120-20180130") new_timerange = TimeRange.parse_timerange("20180120-20180130")
corr_dataframes, base_dataframes = freqai.dd.get_base_and_corr_dataframes( corr_dataframes, base_dataframes = freqai.dd.get_base_and_corr_dataframes(
data_load_timerange, freqai.dk.pair, freqai.dk data_load_timerange, freqai.dk.pair, freqai.dk
) )
unfiltered_dataframe = freqai.dk.use_strategy_to_populate_indicators( unfiltered_dataframe = freqai.dk.use_strategy_to_populate_indicators(
strategy, corr_dataframes, base_dataframes, freqai.dk.pair strategy, corr_dataframes, base_dataframes, freqai.dk.pair
) )
for i in range(5): for i in range(5):
unfiltered_dataframe[f'constant_{i}'] = i unfiltered_dataframe[f"constant_{i}"] = i
unfiltered_dataframe = freqai.dk.slice_dataframe(new_timerange, unfiltered_dataframe) unfiltered_dataframe = freqai.dk.slice_dataframe(new_timerange, unfiltered_dataframe)
@ -212,23 +205,23 @@ def make_data_dictionary(mocker, freqai_conf):
new_timerange = TimeRange.parse_timerange("20180120-20180130") new_timerange = TimeRange.parse_timerange("20180120-20180130")
corr_dataframes, base_dataframes = freqai.dd.get_base_and_corr_dataframes( corr_dataframes, base_dataframes = freqai.dd.get_base_and_corr_dataframes(
data_load_timerange, freqai.dk.pair, freqai.dk data_load_timerange, freqai.dk.pair, freqai.dk
) )
unfiltered_dataframe = freqai.dk.use_strategy_to_populate_indicators( unfiltered_dataframe = freqai.dk.use_strategy_to_populate_indicators(
strategy, corr_dataframes, base_dataframes, freqai.dk.pair strategy, corr_dataframes, base_dataframes, freqai.dk.pair
) )
unfiltered_dataframe = freqai.dk.slice_dataframe(new_timerange, unfiltered_dataframe) unfiltered_dataframe = freqai.dk.slice_dataframe(new_timerange, unfiltered_dataframe)
freqai.dk.find_features(unfiltered_dataframe) freqai.dk.find_features(unfiltered_dataframe)
features_filtered, labels_filtered = freqai.dk.filter_features( features_filtered, labels_filtered = freqai.dk.filter_features(
unfiltered_dataframe, unfiltered_dataframe,
freqai.dk.training_features_list, freqai.dk.training_features_list,
freqai.dk.label_list, freqai.dk.label_list,
training_filter=True, training_filter=True,
) )
data_dictionary = freqai.dk.make_train_test_datasets(features_filtered, labels_filtered) data_dictionary = freqai.dk.make_train_test_datasets(features_filtered, labels_filtered)
@ -247,8 +240,8 @@ def get_freqai_live_analyzed_dataframe(mocker, freqaiconf):
timerange = TimeRange.parse_timerange("20180110-20180114") timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dk.load_all_pair_histories(timerange) freqai.dk.load_all_pair_histories(timerange)
strategy.analyze_pair('ADA/BTC', '5m') strategy.analyze_pair("ADA/BTC", "5m")
return strategy.dp.get_analyzed_dataframe('ADA/BTC', '5m') return strategy.dp.get_analyzed_dataframe("ADA/BTC", "5m")
def get_freqai_analyzed_dataframe(mocker, freqaiconf): def get_freqai_analyzed_dataframe(mocker, freqaiconf):
@ -264,7 +257,7 @@ def get_freqai_analyzed_dataframe(mocker, freqaiconf):
sub_timerange = TimeRange.parse_timerange("20180111-20180114") sub_timerange = TimeRange.parse_timerange("20180111-20180114")
corr_df, base_df = freqai.dk.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC") corr_df, base_df = freqai.dk.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC")
return freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, 'LTC/BTC') return freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
def get_ready_to_train(mocker, freqaiconf): def get_ready_to_train(mocker, freqaiconf):

View File

@ -26,24 +26,25 @@ class ReinforcementLearner_test_3ac(ReinforcementLearner):
""" """
def calculate_reward(self, action: int) -> float: def calculate_reward(self, action: int) -> float:
# first, penalize if the action is not valid # first, penalize if the action is not valid
if not self._is_valid(action): if not self._is_valid(action):
return -2 return -2
pnl = self.get_unrealized_profit() pnl = self.get_unrealized_profit()
rew = np.sign(pnl) * (pnl + 1) rew = np.sign(pnl) * (pnl + 1)
factor = 100. factor = 100.0
# reward agent for entering trades # reward agent for entering trades
if (action in (Actions.Buy.value, Actions.Sell.value) if (
and self._position == Positions.Neutral): action in (Actions.Buy.value, Actions.Sell.value)
and self._position == Positions.Neutral
):
return 25 return 25
# discourage agent from not entering trades # discourage agent from not entering trades
if action == Actions.Neutral.value and self._position == Positions.Neutral: if action == Actions.Neutral.value and self._position == Positions.Neutral:
return -1 return -1
max_trade_duration = self.rl_config.get('max_trade_duration_candles', 300) max_trade_duration = self.rl_config.get("max_trade_duration_candles", 300)
trade_duration = self._current_tick - self._last_trade_tick # type: ignore trade_duration = self._current_tick - self._last_trade_tick # type: ignore
if trade_duration <= max_trade_duration: if trade_duration <= max_trade_duration:
@ -67,4 +68,4 @@ class ReinforcementLearner_test_3ac(ReinforcementLearner):
factor *= self.rl_config["model_reward_parameters"].get("win_reward_factor", 2) factor *= self.rl_config["model_reward_parameters"].get("win_reward_factor", 2)
return float(rew * factor) return float(rew * factor)
return 0. return 0.0

View File

@ -26,24 +26,25 @@ class ReinforcementLearner_test_4ac(ReinforcementLearner):
""" """
def calculate_reward(self, action: int) -> float: def calculate_reward(self, action: int) -> float:
# first, penalize if the action is not valid # first, penalize if the action is not valid
if not self._is_valid(action): if not self._is_valid(action):
return -2 return -2
pnl = self.get_unrealized_profit() pnl = self.get_unrealized_profit()
rew = np.sign(pnl) * (pnl + 1) rew = np.sign(pnl) * (pnl + 1)
factor = 100. factor = 100.0
# reward agent for entering trades # reward agent for entering trades
if (action in (Actions.Long_enter.value, Actions.Short_enter.value) if (
and self._position == Positions.Neutral): action in (Actions.Long_enter.value, Actions.Short_enter.value)
and self._position == Positions.Neutral
):
return 25 return 25
# discourage agent from not entering trades # discourage agent from not entering trades
if action == Actions.Neutral.value and self._position == Positions.Neutral: if action == Actions.Neutral.value and self._position == Positions.Neutral:
return -1 return -1
max_trade_duration = self.rl_config.get('max_trade_duration_candles', 300) max_trade_duration = self.rl_config.get("max_trade_duration_candles", 300)
trade_duration = self._current_tick - self._last_trade_tick # type: ignore trade_duration = self._current_tick - self._last_trade_tick # type: ignore
if trade_duration <= max_trade_duration: if trade_duration <= max_trade_duration:
@ -52,20 +53,22 @@ class ReinforcementLearner_test_4ac(ReinforcementLearner):
factor *= 0.5 factor *= 0.5
# discourage sitting in position # discourage sitting in position
if (self._position in (Positions.Short, Positions.Long) and if (
action == Actions.Neutral.value): self._position in (Positions.Short, Positions.Long)
and action == Actions.Neutral.value
):
return -1 * trade_duration / max_trade_duration return -1 * trade_duration / max_trade_duration
# close long # close long
if action == Actions.Exit.value and self._position == Positions.Long: if action == Actions.Exit.value and self._position == Positions.Long:
if pnl > self.profit_aim * self.rr: if pnl > self.profit_aim * self.rr:
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2) factor *= self.rl_config["model_reward_parameters"].get("win_reward_factor", 2)
return float(rew * factor) return float(rew * factor)
# close short # close short
if action == Actions.Exit.value and self._position == Positions.Short: if action == Actions.Exit.value and self._position == Positions.Short:
if pnl > self.profit_aim * self.rr: if pnl > self.profit_aim * self.rr:
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2) factor *= self.rl_config["model_reward_parameters"].get("win_reward_factor", 2)
return float(rew * factor) return float(rew * factor)
return 0. return 0.0

View File

@ -10,33 +10,40 @@ five_hours = FtPrecise(5.0)
twentyfive_hours = FtPrecise(25.0) twentyfive_hours = FtPrecise(25.0)
@pytest.mark.parametrize('exchange,interest_rate,hours,expected', [ @pytest.mark.parametrize(
('binance', 0.0005, ten_mins, 0.00125), "exchange,interest_rate,hours,expected",
('binance', 0.00025, ten_mins, 0.000625), [
('binance', 0.00025, five_hours, 0.003125), ("binance", 0.0005, ten_mins, 0.00125),
('binance', 0.00025, twentyfive_hours, 0.015625), ("binance", 0.00025, ten_mins, 0.000625),
# Kraken ("binance", 0.00025, five_hours, 0.003125),
('kraken', 0.0005, ten_mins, 0.06), ("binance", 0.00025, twentyfive_hours, 0.015625),
('kraken', 0.00025, ten_mins, 0.03), # Kraken
('kraken', 0.00025, five_hours, 0.045), ("kraken", 0.0005, ten_mins, 0.06),
('kraken', 0.00025, twentyfive_hours, 0.12), ("kraken", 0.00025, ten_mins, 0.03),
]) ("kraken", 0.00025, five_hours, 0.045),
("kraken", 0.00025, twentyfive_hours, 0.12),
],
)
def test_interest(exchange, interest_rate, hours, expected): def test_interest(exchange, interest_rate, hours, expected):
borrowed = FtPrecise(60.0) borrowed = FtPrecise(60.0)
assert pytest.approx(float(interest( assert (
exchange_name=exchange, pytest.approx(
borrowed=borrowed, float(
rate=FtPrecise(interest_rate), interest(
hours=hours exchange_name=exchange,
))) == expected borrowed=borrowed,
rate=FtPrecise(interest_rate),
hours=hours,
)
)
)
== expected
)
def test_interest_exception(): def test_interest_exception():
with pytest.raises(OperationalException, match=r"Leverage not available on .* with freqtrade"): with pytest.raises(OperationalException, match=r"Leverage not available on .* with freqtrade"):
interest( interest(
exchange_name='bitmex', exchange_name="bitmex", borrowed=FtPrecise(60.0), rate=FtPrecise(0.0005), hours=ten_mins
borrowed=FtPrecise(60.0),
rate=FtPrecise(0.0005),
hours=ten_mins
) )

View File

@ -9,13 +9,14 @@ from freqtrade.util.datetime_helpers import dt_utc
tests_start_time = dt_utc(2018, 10, 3) tests_start_time = dt_utc(2018, 10, 3)
tests_timeframe = '1h' tests_timeframe = "1h"
class BTrade(NamedTuple): class BTrade(NamedTuple):
""" """
Minimalistic Trade result used for functional backtesting Minimalistic Trade result used for functional backtesting
""" """
exit_reason: ExitType exit_reason: ExitType
open_tick: int open_tick: int
close_tick: int close_tick: int
@ -27,6 +28,7 @@ class BTContainer(NamedTuple):
""" """
Minimal BacktestContainer defining Backtest inputs and results. Minimal BacktestContainer defining Backtest inputs and results.
""" """
data: List[List[float]] data: List[List[float]]
stop_loss: float stop_loss: float
roi: Dict[str, float] roi: Dict[str, float]
@ -51,22 +53,32 @@ def _get_frame_time_from_offset(offset):
def _build_backtest_dataframe(data): def _build_backtest_dataframe(data):
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'enter_long', 'exit_long', columns = [
'enter_short', 'exit_short'] "date",
"open",
"high",
"low",
"close",
"volume",
"enter_long",
"exit_long",
"enter_short",
"exit_short",
]
if len(data[0]) == 8: if len(data[0]) == 8:
# No short columns # No short columns
data = [d + [0, 0] for d in data] data = [d + [0, 0] for d in data]
columns = columns + ['enter_tag'] if len(data[0]) == 11 else columns columns = columns + ["enter_tag"] if len(data[0]) == 11 else columns
frame = DataFrame.from_records(data, columns=columns) frame = DataFrame.from_records(data, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset) frame["date"] = frame["date"].apply(_get_frame_time_from_offset)
# Ensure floats are in place # Ensure floats are in place
for column in ['open', 'high', 'low', 'close', 'volume']: for column in ["open", "high", "low", "close", "volume"]:
frame[column] = frame[column].astype('float64') frame[column] = frame[column].astype("float64")
# Ensure all candles make kindof sense # Ensure all candles make kindof sense
assert all(frame['low'] <= frame['close']) assert all(frame["low"] <= frame["close"])
assert all(frame['low'] <= frame['open']) assert all(frame["low"] <= frame["open"])
assert all(frame['high'] >= frame['close']) assert all(frame["high"] >= frame["close"])
assert all(frame['high'] >= frame['open']) assert all(frame["high"] >= frame["open"])
return frame return frame

View File

@ -11,21 +11,23 @@ from freqtrade.optimize.hyperopt import Hyperopt
from tests.conftest import patch_exchange from tests.conftest import patch_exchange
@pytest.fixture(scope='function') @pytest.fixture(scope="function")
def hyperopt_conf(default_conf): def hyperopt_conf(default_conf):
hyperconf = deepcopy(default_conf) hyperconf = deepcopy(default_conf)
hyperconf.update({ hyperconf.update(
'datadir': Path(default_conf['datadir']), {
'runmode': RunMode.HYPEROPT, "datadir": Path(default_conf["datadir"]),
'strategy': 'HyperoptableStrategy', "runmode": RunMode.HYPEROPT,
'hyperopt_loss': 'ShortTradeDurHyperOptLoss', "strategy": "HyperoptableStrategy",
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), "hyperopt_loss": "ShortTradeDurHyperOptLoss",
'epochs': 1, "hyperopt_path": str(Path(__file__).parent / "hyperopts"),
'timerange': None, "epochs": 1,
'spaces': ['default'], "timerange": None,
'hyperopt_jobs': 1, "spaces": ["default"],
'hyperopt_min_trades': 1, "hyperopt_jobs": 1,
}) "hyperopt_min_trades": 1,
}
)
return hyperconf return hyperconf
@ -36,32 +38,29 @@ def backtesting_cleanup():
Backtesting.cleanup() Backtesting.cleanup()
@pytest.fixture(scope='function') @pytest.fixture(scope="function")
def hyperopt(hyperopt_conf, mocker): def hyperopt(hyperopt_conf, mocker):
patch_exchange(mocker) patch_exchange(mocker)
return Hyperopt(hyperopt_conf) return Hyperopt(hyperopt_conf)
@pytest.fixture(scope='function') @pytest.fixture(scope="function")
def hyperopt_results(): def hyperopt_results():
return pd.DataFrame( return pd.DataFrame(
{ {
'pair': ['ETH/USDT', 'ETH/USDT', 'ETH/USDT', 'ETH/USDT'], "pair": ["ETH/USDT", "ETH/USDT", "ETH/USDT", "ETH/USDT"],
'profit_ratio': [-0.1, 0.2, -0.12, 0.3], "profit_ratio": [-0.1, 0.2, -0.12, 0.3],
'profit_abs': [-0.2, 0.4, -0.21, 0.6], "profit_abs": [-0.2, 0.4, -0.21, 0.6],
'trade_duration': [10, 30, 10, 10], "trade_duration": [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1], "amount": [0.1, 0.1, 0.1, 0.1],
'exit_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI], "exit_reason": [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI],
'open_date': "open_date": [
[
datetime(2019, 1, 1, 9, 15, 0), datetime(2019, 1, 1, 9, 15, 0),
datetime(2019, 1, 2, 8, 55, 0), datetime(2019, 1, 2, 8, 55, 0),
datetime(2019, 1, 3, 9, 15, 0), datetime(2019, 1, 3, 9, 15, 0),
datetime(2019, 1, 4, 9, 15, 0), datetime(2019, 1, 4, 9, 15, 0),
], ],
'close_date': "close_date": [
[
datetime(2019, 1, 1, 9, 25, 0), datetime(2019, 1, 1, 9, 25, 0),
datetime(2019, 1, 2, 9, 25, 0), datetime(2019, 1, 2, 9, 25, 0),
datetime(2019, 1, 3, 9, 25, 0), datetime(2019, 1, 3, 9, 25, 0),

File diff suppressed because it is too large Load Diff

View File

@ -16,25 +16,26 @@ from tests.conftest import EXMS, patch_exchange
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_exit_signal'] = False default_conf["use_exit_signal"] = False
default_conf['max_open_trades'] = 10 default_conf["max_open_trades"] = 10
mocker.patch(f'{EXMS}.get_fee', fee) mocker.patch(f"{EXMS}.get_fee", fee)
mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision', mocker.patch(
lambda x, *args, **kwargs: round(x, 8)) "freqtrade.optimize.backtesting.amount_to_contract_precision",
lambda x, *args, **kwargs: round(x, 8),
)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
patch_exchange(mocker) patch_exchange(mocker)
default_conf.update({ default_conf.update(
"stake_amount": 100.0, {"stake_amount": 100.0, "dry_run_wallet": 1000.0, "strategy": "StrategyTestV3"}
"dry_run_wallet": 1000.0, )
"strategy": "StrategyTestV3"
})
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC' pair = "UNITTEST/BTC"
timerange = TimeRange('date', None, 1517227800, 0) timerange = TimeRange("date", None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], data = history.load_data(
timerange=timerange) datadir=testdatadir, timeframe="5m", pairs=["UNITTEST/BTC"], timerange=timerange
)
backtesting.strategy.position_adjustment_enable = True backtesting.strategy.position_adjustment_enable = True
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
@ -43,47 +44,50 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
start_date=min_date, start_date=min_date,
end_date=max_date, end_date=max_date,
) )
results = result['results'] results = result["results"]
assert not results.empty assert not results.empty
assert len(results) == 2 assert len(results) == 2
expected = pd.DataFrame( expected = pd.DataFrame(
{'pair': [pair, pair], {
'stake_amount': [500.0, 100.0], "pair": [pair, pair],
'max_stake_amount': [500.0, 100], "stake_amount": [500.0, 100.0],
'amount': [4806.87657523, 970.63960782], "max_stake_amount": [500.0, 100],
'open_date': pd.to_datetime([dt_utc(2018, 1, 29, 18, 40, 0), "amount": [4806.87657523, 970.63960782],
dt_utc(2018, 1, 30, 3, 30, 0)], utc=True "open_date": pd.to_datetime(
), [dt_utc(2018, 1, 29, 18, 40, 0), dt_utc(2018, 1, 30, 3, 30, 0)], utc=True
'close_date': pd.to_datetime([dt_utc(2018, 1, 29, 22, 00, 0), ),
dt_utc(2018, 1, 30, 4, 10, 0)], utc=True), "close_date": pd.to_datetime(
'open_rate': [0.10401764891917063, 0.10302485], [dt_utc(2018, 1, 29, 22, 00, 0), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True
'close_rate': [0.10453904064307624, 0.10354126528822055], ),
'fee_open': [0.0025, 0.0025], "open_rate": [0.10401764891917063, 0.10302485],
'fee_close': [0.0025, 0.0025], "close_rate": [0.10453904064307624, 0.10354126528822055],
'trade_duration': [200, 40], "fee_open": [0.0025, 0.0025],
'profit_ratio': [0.0, 0.0], "fee_close": [0.0025, 0.0025],
'profit_abs': [0.0, 0.0], "trade_duration": [200, 40],
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value], "profit_ratio": [0.0, 0.0],
'initial_stop_loss_abs': [0.0940005, 0.092722365], "profit_abs": [0.0, 0.0],
'initial_stop_loss_ratio': [-0.1, -0.1], "exit_reason": [ExitType.ROI.value, ExitType.ROI.value],
'stop_loss_abs': [0.0940005, 0.092722365], "initial_stop_loss_abs": [0.0940005, 0.092722365],
'stop_loss_ratio': [-0.1, -0.1], "initial_stop_loss_ratio": [-0.1, -0.1],
'min_rate': [0.10370188, 0.10300000000000001], "stop_loss_abs": [0.0940005, 0.092722365],
'max_rate': [0.10481985, 0.10388887000000001], "stop_loss_ratio": [-0.1, -0.1],
'is_open': [False, False], "min_rate": [0.10370188, 0.10300000000000001],
'enter_tag': ['', ''], "max_rate": [0.10481985, 0.10388887000000001],
'leverage': [1.0, 1.0], "is_open": [False, False],
'is_short': [False, False], "enter_tag": ["", ""],
'open_timestamp': [1517251200000, 1517283000000], "leverage": [1.0, 1.0],
'close_timestamp': [1517263200000, 1517285400000], "is_short": [False, False],
}) "open_timestamp": [1517251200000, 1517283000000],
results_no = results.drop(columns=['orders']) "close_timestamp": [1517263200000, 1517285400000],
}
)
results_no = results.drop(columns=["orders"])
pd.testing.assert_frame_equal(results_no, expected, check_exact=True) pd.testing.assert_frame_equal(results_no, expected, check_exact=True)
data_pair = processed[pair] data_pair = processed[pair]
assert len(results.iloc[0]['orders']) == 6 assert len(results.iloc[0]["orders"]) == 6
assert len(results.iloc[1]['orders']) == 2 assert len(results.iloc[1]["orders"]) == 2
for _, t in results.iterrows(): for _, t in results.iterrows():
ln = data_pair.loc[data_pair["date"] == t["open_date"]] ln = data_pair.loc[data_pair["date"] == t["open_date"]]
@ -91,65 +95,65 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
assert ln is not None assert ln is not None
# check close trade rate aligns to close rate or is between high and low # check close trade rate aligns to close rate or is between high and low
ln = data_pair.loc[data_pair["date"] == t["close_date"]] ln = data_pair.loc[data_pair["date"] == t["close_date"]]
assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or assert round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or round(
round(ln.iloc[0]["low"], 6) < round( ln.iloc[0]["low"], 6
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)) ) < round(t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)
@pytest.mark.parametrize('leverage', [ @pytest.mark.parametrize("leverage", [1, 2])
1, 2
])
def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, leverage) -> None: def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, leverage) -> None:
default_conf['use_exit_signal'] = False default_conf["use_exit_signal"] = False
mocker.patch(f'{EXMS}.get_fee', fee) mocker.patch(f"{EXMS}.get_fee", fee)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.1, 0.1)) mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.1, 0.1))
mocker.patch('freqtrade.optimize.backtesting.Backtesting._run_funding_fees') mocker.patch("freqtrade.optimize.backtesting.Backtesting._run_funding_fees")
patch_exchange(mocker) patch_exchange(mocker)
default_conf.update({ default_conf.update(
"stake_amount": 100.0, {
"dry_run_wallet": 1000.0, "stake_amount": 100.0,
"strategy": "StrategyTestV3", "dry_run_wallet": 1000.0,
"trading_mode": "futures", "strategy": "StrategyTestV3",
"margin_mode": "isolated", "trading_mode": "futures",
}) "margin_mode": "isolated",
default_conf['pairlists'] = [{'method': 'StaticPairList', 'allow_inactive': True}] }
)
default_conf["pairlists"] = [{"method": "StaticPairList", "allow_inactive": True}]
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._can_short = True backtesting._can_short = True
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
pair = 'XRP/USDT:USDT' pair = "XRP/USDT:USDT"
row_enter = [ row_enter = [
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=0), pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=0),
2.1, # Open 2.1, # Open
2.2, # High 2.2, # High
1.9, # Low 1.9, # Low
2.1, # Close 2.1, # Close
1, # enter_long 1, # enter_long
0, # exit_long 0, # exit_long
0, # enter_short 0, # enter_short
0, # exit_short 0, # exit_short
'', # enter_tag "", # enter_tag
'', # exit_tag "", # exit_tag
] ]
# Exit row - with slightly different values # Exit row - with slightly different values
row_exit = [ row_exit = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
2.2, # Open 2.2, # Open
2.3, # High 2.3, # High
2.0, # Low 2.0, # Low
2.2, # Close 2.2, # Close
1, # enter_long 1, # enter_long
0, # exit_long 0, # exit_long
0, # enter_short 0, # enter_short
0, # exit_short 0, # exit_short
'', # enter_tag "", # enter_tag
'', # exit_tag "", # exit_tag
] ]
backtesting.strategy.leverage = MagicMock(return_value=leverage) backtesting.strategy.leverage = MagicMock(return_value=leverage)
trade = backtesting._enter_trade(pair, row=row_enter, direction='long') trade = backtesting._enter_trade(pair, row=row_enter, direction="long")
current_time = row_enter[0].to_pydatetime() current_time = row_enter[0].to_pydatetime()
assert trade assert trade
assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.stake_amount) == 100.0
@ -164,7 +168,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.amount) == 47.61904762 * leverage assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 1 assert len(trade.orders) == 1
# Increase position by 100 # Increase position by 100
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, 'PartIncrease')) backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, "PartIncrease"))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_enter, current_time) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_enter, current_time)
@ -173,7 +177,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.stake_amount) == 200.0 assert pytest.approx(trade.stake_amount) == 200.0
assert pytest.approx(trade.amount) == 95.23809524 * leverage assert pytest.approx(trade.amount) == 95.23809524 * leverage
assert len(trade.orders) == 2 assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_tag == 'PartIncrease' assert trade.orders[-1].ft_order_tag == "PartIncrease"
assert pytest.approx(trade.liquidation_price) == liq_price assert pytest.approx(trade.liquidation_price) == liq_price
# Reduce by more than amount - no change to trade. # Reduce by more than amount - no change to trade.
@ -190,14 +194,14 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.liquidation_price) == liq_price assert pytest.approx(trade.liquidation_price) == liq_price
# Reduce position by 50 # Reduce position by 50
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, 'partDecrease')) backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, "partDecrease"))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time)
assert trade assert trade
assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.stake_amount) == 100.0
assert pytest.approx(trade.amount) == 47.61904762 * leverage assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 3 assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_tag == 'partDecrease' assert trade.orders[-1].ft_order_tag == "partDecrease"
assert trade.nr_of_successful_entries == 2 assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1 assert trade.nr_of_successful_exits == 1
assert pytest.approx(trade.liquidation_price) == liq_price assert pytest.approx(trade.liquidation_price) == liq_price

View File

@ -32,161 +32,162 @@ def test_parse_args_backtesting(mocker) -> None:
further argument parsing is done in test_arguments.py further argument parsing is done in test_arguments.py
""" """
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True])) mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
backtesting_mock = mocker.patch('freqtrade.commands.start_backtesting') backtesting_mock = mocker.patch("freqtrade.commands.start_backtesting")
backtesting_mock.__name__ = PropertyMock("start_backtesting") backtesting_mock.__name__ = PropertyMock("start_backtesting")
# it's sys.exit(0) at the end of backtesting # it's sys.exit(0) at the end of backtesting
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(['backtesting']) main(["backtesting"])
assert backtesting_mock.call_count == 1 assert backtesting_mock.call_count == 1
call_args = backtesting_mock.call_args[0][0] call_args = backtesting_mock.call_args[0][0]
assert call_args['config'] == ['config.json'] assert call_args["config"] == ["config.json"]
assert call_args['verbosity'] == 0 assert call_args["verbosity"] == 0
assert call_args['command'] == 'backtesting' assert call_args["command"] == "backtesting"
assert call_args['func'] is not None assert call_args["func"] is not None
assert callable(call_args['func']) assert callable(call_args["func"])
assert call_args['timeframe'] is None assert call_args["timeframe"] is None
def test_main_start_hyperopt(mocker) -> None: def test_main_start_hyperopt(mocker) -> None:
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True])) mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock()) hyperopt_mock = mocker.patch("freqtrade.commands.start_hyperopt", MagicMock())
hyperopt_mock.__name__ = PropertyMock('start_hyperopt') hyperopt_mock.__name__ = PropertyMock("start_hyperopt")
# it's sys.exit(0) at the end of hyperopt # it's sys.exit(0) at the end of hyperopt
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(['hyperopt']) main(["hyperopt"])
assert hyperopt_mock.call_count == 1 assert hyperopt_mock.call_count == 1
call_args = hyperopt_mock.call_args[0][0] call_args = hyperopt_mock.call_args[0][0]
assert call_args['config'] == ['config.json'] assert call_args["config"] == ["config.json"]
assert call_args['verbosity'] == 0 assert call_args["verbosity"] == 0
assert call_args['command'] == 'hyperopt' assert call_args["command"] == "hyperopt"
assert call_args['func'] is not None assert call_args["func"] is not None
assert callable(call_args['func']) assert callable(call_args["func"])
def test_main_fatal_exception(mocker, default_conf, caplog) -> None: def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cleanup", MagicMock())
mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=Exception)) mocker.patch("freqtrade.worker.Worker._worker", MagicMock(side_effect=Exception))
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) mocker.patch("freqtrade.freqtradebot.init_db", MagicMock())
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json'] args = ["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"]
# Test Main + the KeyboardInterrupt exception # Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog) assert log_has("Using config: tests/testdata/testconfigs/main_test_config.json ...", caplog)
assert log_has('Fatal exception!', caplog) assert log_has("Fatal exception!", caplog)
def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cleanup", MagicMock())
mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=KeyboardInterrupt)) mocker.patch("freqtrade.worker.Worker._worker", MagicMock(side_effect=KeyboardInterrupt))
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) mocker.patch("freqtrade.freqtradebot.init_db", MagicMock())
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json'] args = ["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"]
# Test Main + the KeyboardInterrupt exception # Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog) assert log_has("Using config: tests/testdata/testconfigs/main_test_config.json ...", caplog)
assert log_has('SIGINT received, aborting ...', caplog) assert log_has("SIGINT received, aborting ...", caplog)
def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_operational_exception(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cleanup", MagicMock())
mocker.patch( mocker.patch(
'freqtrade.worker.Worker._worker', "freqtrade.worker.Worker._worker", MagicMock(side_effect=FreqtradeException("Oh snap!"))
MagicMock(side_effect=FreqtradeException('Oh snap!'))
) )
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) mocker.patch("freqtrade.freqtradebot.init_db", MagicMock())
args = ['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json'] args = ["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"]
# Test Main + the KeyboardInterrupt exception # Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog) assert log_has("Using config: tests/testdata/testconfigs/main_test_config.json ...", caplog)
assert log_has('Oh snap!', caplog) assert log_has("Oh snap!", caplog)
def test_main_operational_exception1(mocker, default_conf, caplog) -> None: def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch( mocker.patch(
'freqtrade.commands.list_commands.list_available_exchanges', "freqtrade.commands.list_commands.list_available_exchanges",
MagicMock(side_effect=ValueError('Oh snap!')) MagicMock(side_effect=ValueError("Oh snap!")),
) )
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
args = ['list-exchanges'] args = ["list-exchanges"]
# Test Main + the KeyboardInterrupt exception # Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has('Fatal exception!', caplog) assert log_has("Fatal exception!", caplog)
assert not log_has_re(r'SIGINT.*', caplog) assert not log_has_re(r"SIGINT.*", caplog)
mocker.patch( mocker.patch(
'freqtrade.commands.list_commands.list_available_exchanges', "freqtrade.commands.list_commands.list_available_exchanges",
MagicMock(side_effect=KeyboardInterrupt) MagicMock(side_effect=KeyboardInterrupt),
) )
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has_re(r'SIGINT.*', caplog) assert log_has_re(r"SIGINT.*", caplog)
def test_main_ConfigurationError(mocker, default_conf, caplog) -> None: def test_main_ConfigurationError(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch( mocker.patch(
'freqtrade.commands.list_commands.list_available_exchanges', "freqtrade.commands.list_commands.list_available_exchanges",
MagicMock(side_effect=ConfigurationError('Oh snap!')) MagicMock(side_effect=ConfigurationError("Oh snap!")),
) )
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
args = ['list-exchanges'] args = ["list-exchanges"]
# Test Main + the KeyboardInterrupt exception # Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(args) main(args)
assert log_has_re('Configuration error: Oh snap!', caplog) assert log_has_re("Configuration error: Oh snap!", caplog)
def test_main_reload_config(mocker, default_conf, caplog) -> None: def test_main_reload_config(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cleanup", MagicMock())
# Simulate Running, reload, running workflow # Simulate Running, reload, running workflow
worker_mock = MagicMock(side_effect=[State.RUNNING, worker_mock = MagicMock(
State.RELOAD_CONFIG, side_effect=[
State.RUNNING, State.RUNNING,
OperationalException("Oh snap!")]) State.RELOAD_CONFIG,
mocker.patch('freqtrade.worker.Worker._worker', worker_mock) State.RUNNING,
OperationalException("Oh snap!"),
]
)
mocker.patch("freqtrade.worker.Worker._worker", worker_mock)
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock()) reconfigure_mock = mocker.patch("freqtrade.worker.Worker._reconfigure", MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) mocker.patch("freqtrade.freqtradebot.init_db", MagicMock())
args = Arguments([ args = Arguments(
'trade', ["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"]
'-c', ).get_parsed_arg()
'tests/testdata/testconfigs/main_test_config.json'
]).get_parsed_arg()
worker = Worker(args=args, config=default_conf) worker = Worker(args=args, config=default_conf)
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
main(['trade', '-c', 'tests/testdata/testconfigs/main_test_config.json']) main(["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"])
assert log_has('Using config: tests/testdata/testconfigs/main_test_config.json ...', caplog) assert log_has("Using config: tests/testdata/testconfigs/main_test_config.json ...", caplog)
assert worker_mock.call_count == 4 assert worker_mock.call_count == 4
assert reconfigure_mock.call_count == 1 assert reconfigure_mock.call_count == 1
assert isinstance(worker.freqtrade, FreqtradeBot) assert isinstance(worker.freqtrade, FreqtradeBot)
@ -194,27 +195,24 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None:
def test_reconfigure(mocker, default_conf) -> None: def test_reconfigure(mocker, default_conf) -> None:
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cleanup", MagicMock())
mocker.patch( mocker.patch(
'freqtrade.worker.Worker._worker', "freqtrade.worker.Worker._worker", MagicMock(side_effect=OperationalException("Oh snap!"))
MagicMock(side_effect=OperationalException('Oh snap!'))
) )
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch("freqtrade.freqtradebot.RPCManager", MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) mocker.patch("freqtrade.freqtradebot.init_db", MagicMock())
args = Arguments([ args = Arguments(
'trade', ["trade", "-c", "tests/testdata/testconfigs/main_test_config.json"]
'-c', ).get_parsed_arg()
'tests/testdata/testconfigs/main_test_config.json'
]).get_parsed_arg()
worker = Worker(args=args, config=default_conf) worker = Worker(args=args, config=default_conf)
freqtrade = worker.freqtrade freqtrade = worker.freqtrade
# Renew mock to return modified data # Renew mock to return modified data
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] += 1 conf["stake_amount"] += 1
patched_configuration_load_config_file(mocker, conf) patched_configuration_load_config_file(mocker, conf)
worker._config = conf worker._config = conf
@ -224,4 +222,4 @@ def test_reconfigure(mocker, default_conf) -> None:
# Verify we have a new instance with the new config # Verify we have a new instance with the new config
assert freqtrade is not freqtrade2 assert freqtrade is not freqtrade2
assert freqtrade.config['stake_amount'] + 1 == freqtrade2.config['stake_amount'] assert freqtrade.config["stake_amount"] + 1 == freqtrade2.config["stake_amount"]

View File

@ -23,33 +23,31 @@ from freqtrade.misc import (
def test_file_dump_json(mocker) -> None: def test_file_dump_json(mocker) -> None:
file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock()) file_open = mocker.patch("freqtrade.misc.Path.open", MagicMock())
json_dump = mocker.patch('rapidjson.dump', MagicMock()) json_dump = mocker.patch("rapidjson.dump", MagicMock())
file_dump_json(Path('somefile'), [1, 2, 3]) file_dump_json(Path("somefile"), [1, 2, 3])
assert file_open.call_count == 1 assert file_open.call_count == 1
assert json_dump.call_count == 1 assert json_dump.call_count == 1
file_open = mocker.patch('freqtrade.misc.gzip.open', MagicMock()) file_open = mocker.patch("freqtrade.misc.gzip.open", MagicMock())
json_dump = mocker.patch('rapidjson.dump', MagicMock()) json_dump = mocker.patch("rapidjson.dump", MagicMock())
file_dump_json(Path('somefile'), [1, 2, 3], True) file_dump_json(Path("somefile"), [1, 2, 3], True)
assert file_open.call_count == 1 assert file_open.call_count == 1
assert json_dump.call_count == 1 assert json_dump.call_count == 1
def test_file_load_json(mocker, testdatadir) -> None: def test_file_load_json(mocker, testdatadir) -> None:
# 7m .json does not exist # 7m .json does not exist
ret = file_load_json(testdatadir / 'UNITTEST_BTC-7m.json') ret = file_load_json(testdatadir / "UNITTEST_BTC-7m.json")
assert not ret assert not ret
# 1m json exists (but no .gz exists) # 1m json exists (but no .gz exists)
ret = file_load_json(testdatadir / 'UNITTEST_BTC-1m.json') ret = file_load_json(testdatadir / "UNITTEST_BTC-1m.json")
assert ret assert ret
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json # 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
ret = file_load_json(testdatadir / 'UNITTEST_BTC-8m.json') ret = file_load_json(testdatadir / "UNITTEST_BTC-8m.json")
assert ret assert ret
def test_is_file_in_dir(tmp_path): def test_is_file_in_dir(tmp_path):
# Create a temporary directory and file # Create a temporary directory and file
dir_path = tmp_path / "subdir" dir_path = tmp_path / "subdir"
dir_path.mkdir() dir_path.mkdir()
@ -66,69 +64,72 @@ def test_is_file_in_dir(tmp_path):
assert is_file_in_dir(file_path2, tmp_path) is False assert is_file_in_dir(file_path2, tmp_path) is False
@pytest.mark.parametrize("pair,expected_result", [ @pytest.mark.parametrize(
("ETH/BTC", 'ETH_BTC'), "pair,expected_result",
("ETH/USDT", 'ETH_USDT'), [
("ETH/USDT:USDT", 'ETH_USDT_USDT'), # swap with USDT as settlement currency ("ETH/BTC", "ETH_BTC"),
("ETH/USD:USD", 'ETH_USD_USD'), # swap with USD as settlement currency ("ETH/USDT", "ETH_USDT"),
("AAVE/USD:USD", 'AAVE_USD_USD'), # swap with USDT as settlement currency ("ETH/USDT:USDT", "ETH_USDT_USDT"), # swap with USDT as settlement currency
("ETH/USDT:USDT-210625", 'ETH_USDT_USDT-210625'), # expiring futures ("ETH/USD:USD", "ETH_USD_USD"), # swap with USD as settlement currency
("Fabric Token/ETH", 'Fabric_Token_ETH'), ("AAVE/USD:USD", "AAVE_USD_USD"), # swap with USDT as settlement currency
("ETHH20", 'ETHH20'), ("ETH/USDT:USDT-210625", "ETH_USDT_USDT-210625"), # expiring futures
(".XBTBON2H", '_XBTBON2H'), ("Fabric Token/ETH", "Fabric_Token_ETH"),
("ETHUSD.d", 'ETHUSD_d'), ("ETHH20", "ETHH20"),
("ADA-0327", 'ADA-0327'), (".XBTBON2H", "_XBTBON2H"),
("BTC-USD-200110", 'BTC-USD-200110'), ("ETHUSD.d", "ETHUSD_d"),
("BTC-PERP:USDT", 'BTC-PERP_USDT'), ("ADA-0327", "ADA-0327"),
("F-AKRO/USDT", 'F-AKRO_USDT'), ("BTC-USD-200110", "BTC-USD-200110"),
("LC+/ETH", 'LC__ETH'), ("BTC-PERP:USDT", "BTC-PERP_USDT"),
("CMT@18/ETH", 'CMT_18_ETH'), ("F-AKRO/USDT", "F-AKRO_USDT"),
("LBTC:1022/SAI", 'LBTC_1022_SAI'), ("LC+/ETH", "LC__ETH"),
("$PAC/BTC", '_PAC_BTC'), ("CMT@18/ETH", "CMT_18_ETH"),
("ACC_OLD/BTC", 'ACC_OLD_BTC'), ("LBTC:1022/SAI", "LBTC_1022_SAI"),
]) ("$PAC/BTC", "_PAC_BTC"),
("ACC_OLD/BTC", "ACC_OLD_BTC"),
],
)
def test_pair_to_filename(pair, expected_result): def test_pair_to_filename(pair, expected_result):
pair_s = pair_to_filename(pair) pair_s = pair_to_filename(pair)
assert pair_s == expected_result assert pair_s == expected_result
def test_safe_value_fallback(): def test_safe_value_fallback():
dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} dict1 = {"keya": None, "keyb": 2, "keyc": 5, "keyd": None}
assert safe_value_fallback(dict1, 'keya', 'keyb') == 2 assert safe_value_fallback(dict1, "keya", "keyb") == 2
assert safe_value_fallback(dict1, 'keyb', 'keya') == 2 assert safe_value_fallback(dict1, "keyb", "keya") == 2
assert safe_value_fallback(dict1, 'keyb', 'keyc') == 2 assert safe_value_fallback(dict1, "keyb", "keyc") == 2
assert safe_value_fallback(dict1, 'keya', 'keyc') == 5 assert safe_value_fallback(dict1, "keya", "keyc") == 5
assert safe_value_fallback(dict1, 'keyc', 'keyb') == 5 assert safe_value_fallback(dict1, "keyc", "keyb") == 5
assert safe_value_fallback(dict1, 'keya', 'keyd') is None assert safe_value_fallback(dict1, "keya", "keyd") is None
assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None assert safe_value_fallback(dict1, "keyNo", "keyNo") is None
assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55 assert safe_value_fallback(dict1, "keyNo", "keyNo", 55) == 55
assert safe_value_fallback(dict1, 'keyNo', default_value=55) == 55 assert safe_value_fallback(dict1, "keyNo", default_value=55) == 55
assert safe_value_fallback(dict1, 'keyNo', None, default_value=55) == 55 assert safe_value_fallback(dict1, "keyNo", None, default_value=55) == 55
def test_safe_value_fallback2(): def test_safe_value_fallback2():
dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} dict1 = {"keya": None, "keyb": 2, "keyc": 5, "keyd": None}
dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None} dict2 = {"keya": 20, "keyb": None, "keyc": 6, "keyd": None}
assert safe_value_fallback2(dict1, dict2, 'keya', 'keya') == 20 assert safe_value_fallback2(dict1, dict2, "keya", "keya") == 20
assert safe_value_fallback2(dict2, dict1, 'keya', 'keya') == 20 assert safe_value_fallback2(dict2, dict1, "keya", "keya") == 20
assert safe_value_fallback2(dict1, dict2, 'keyb', 'keyb') == 2 assert safe_value_fallback2(dict1, dict2, "keyb", "keyb") == 2
assert safe_value_fallback2(dict2, dict1, 'keyb', 'keyb') == 2 assert safe_value_fallback2(dict2, dict1, "keyb", "keyb") == 2
assert safe_value_fallback2(dict1, dict2, 'keyc', 'keyc') == 5 assert safe_value_fallback2(dict1, dict2, "keyc", "keyc") == 5
assert safe_value_fallback2(dict2, dict1, 'keyc', 'keyc') == 6 assert safe_value_fallback2(dict2, dict1, "keyc", "keyc") == 6
assert safe_value_fallback2(dict1, dict2, 'keyd', 'keyd') is None assert safe_value_fallback2(dict1, dict2, "keyd", "keyd") is None
assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd') is None assert safe_value_fallback2(dict2, dict1, "keyd", "keyd") is None
assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 assert safe_value_fallback2(dict2, dict1, "keyd", "keyd", 1234) == 1234
assert safe_value_fallback2(dict1, dict2, 'keyNo', 'keyNo') is None assert safe_value_fallback2(dict1, dict2, "keyNo", "keyNo") is None
assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo') is None assert safe_value_fallback2(dict2, dict1, "keyNo", "keyNo") is None
assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 assert safe_value_fallback2(dict2, dict1, "keyNo", "keyNo", 1234) == 1234
def test_plural() -> None: def test_plural() -> None:
@ -163,38 +164,51 @@ def test_plural() -> None:
assert plural(-1.5, "ox", "oxen") == "oxen" assert plural(-1.5, "ox", "oxen") == "oxen"
@pytest.mark.parametrize('conn_url,expected', [ @pytest.mark.parametrize(
("postgresql+psycopg2://scott123:scott123@host:1245/dbname", "conn_url,expected",
"postgresql+psycopg2://scott123:*****@host:1245/dbname"), [
("postgresql+psycopg2://scott123:scott123@host.name.com/dbname", (
"postgresql+psycopg2://scott123:*****@host.name.com/dbname"), "postgresql+psycopg2://scott123:scott123@host:1245/dbname",
("mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company", "postgresql+psycopg2://scott123:*****@host:1245/dbname",
"mariadb+mariadbconnector://app_user:*****@127.0.0.1:3306/company"), ),
("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4", (
"mysql+pymysql://user:*****@some_mariadb/dbname?charset=utf8mb4"), "postgresql+psycopg2://scott123:scott123@host.name.com/dbname",
("sqlite:////freqtrade/user_data/tradesv3.sqlite", "postgresql+psycopg2://scott123:*****@host.name.com/dbname",
"sqlite:////freqtrade/user_data/tradesv3.sqlite"), ),
]) (
"mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company",
"mariadb+mariadbconnector://app_user:*****@127.0.0.1:3306/company",
),
(
"mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4",
"mysql+pymysql://user:*****@some_mariadb/dbname?charset=utf8mb4",
),
(
"sqlite:////freqtrade/user_data/tradesv3.sqlite",
"sqlite:////freqtrade/user_data/tradesv3.sqlite",
),
],
)
def test_parse_db_uri_for_logging(conn_url, expected) -> None: def test_parse_db_uri_for_logging(conn_url, expected) -> None:
assert parse_db_uri_for_logging(conn_url) == expected assert parse_db_uri_for_logging(conn_url) == expected
def test_deep_merge_dicts(): def test_deep_merge_dicts():
a = {'first': {'rows': {'pass': 'dog', 'number': '1', 'test': None}}} a = {"first": {"rows": {"pass": "dog", "number": "1", "test": None}}}
b = {'first': {'rows': {'fail': 'cat', 'number': '5', 'test': 'asdf'}}} b = {"first": {"rows": {"fail": "cat", "number": "5", "test": "asdf"}}}
res = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '5', 'test': 'asdf'}}} res = {"first": {"rows": {"pass": "dog", "fail": "cat", "number": "5", "test": "asdf"}}}
res2 = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '1', 'test': None}}} res2 = {"first": {"rows": {"pass": "dog", "fail": "cat", "number": "1", "test": None}}}
assert deep_merge_dicts(b, deepcopy(a)) == res assert deep_merge_dicts(b, deepcopy(a)) == res
assert deep_merge_dicts(a, deepcopy(b)) == res2 assert deep_merge_dicts(a, deepcopy(b)) == res2
res2['first']['rows']['test'] = 'asdf' res2["first"]["rows"]["test"] = "asdf"
assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2 assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2
def test_dataframe_json(ohlcv_history): def test_dataframe_json(ohlcv_history):
from pandas.testing import assert_frame_equal from pandas.testing import assert_frame_equal
json = dataframe_to_json(ohlcv_history) json = dataframe_to_json(ohlcv_history)
dataframe = json_to_dataframe(json) dataframe = json_to_dataframe(json)
@ -202,7 +216,7 @@ def test_dataframe_json(ohlcv_history):
assert len(ohlcv_history) == len(dataframe) assert len(ohlcv_history) == len(dataframe)
assert_frame_equal(ohlcv_history, dataframe) assert_frame_equal(ohlcv_history, dataframe)
ohlcv_history.at[1, 'date'] = pd.NaT ohlcv_history.at[1, "date"] = pd.NaT
json = dataframe_to_json(ohlcv_history) json = dataframe_to_json(ohlcv_history)
dataframe = json_to_dataframe(json) dataframe = json_to_dataframe(json)

View File

@ -31,7 +31,7 @@ from tests.conftest import get_args, log_has, log_has_re, patch_exchange
def fig_generating_mock(fig, *args, **kwargs): def fig_generating_mock(fig, *args, **kwargs):
""" Return Fig - used to mock add_indicators and plot_trades""" """Return Fig - used to mock add_indicators and plot_trades"""
return fig return fig
@ -51,18 +51,18 @@ def generate_empty_figure():
def test_init_plotscript(default_conf, mocker, testdatadir): def test_init_plotscript(default_conf, mocker, testdatadir):
default_conf['timerange'] = "20180110-20180112" default_conf["timerange"] = "20180110-20180112"
default_conf['trade_source'] = "file" default_conf["trade_source"] = "file"
default_conf['timeframe'] = "5m" default_conf["timeframe"] = "5m"
default_conf['exportfilename'] = testdatadir / "backtest-result.json" default_conf["exportfilename"] = testdatadir / "backtest-result.json"
supported_markets = ["TRX/BTC", "ADA/BTC"] supported_markets = ["TRX/BTC", "ADA/BTC"]
ret = init_plotscript(default_conf, supported_markets) ret = init_plotscript(default_conf, supported_markets)
assert "ohlcv" in ret assert "ohlcv" in ret
assert "trades" in ret assert "trades" in ret
assert "pairs" in ret assert "pairs" in ret
assert 'timerange' in ret assert "timerange" in ret
default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"] default_conf["pairs"] = ["TRX/BTC", "ADA/BTC"]
ret = init_plotscript(default_conf, supported_markets, 20) ret = init_plotscript(default_conf, supported_markets, 20)
assert "ohlcv" in ret assert "ohlcv" in ret
assert "TRX/BTC" in ret["ohlcv"] assert "TRX/BTC" in ret["ohlcv"]
@ -73,15 +73,16 @@ def test_add_indicators(default_conf, testdatadir, caplog):
pair = "UNITTEST/BTC" pair = "UNITTEST/BTC"
timerange = TimeRange() timerange = TimeRange()
data = history.load_pair_history(pair=pair, timeframe='1m', data = history.load_pair_history(
datadir=testdatadir, timerange=timerange) pair=pair, timeframe="1m", datadir=testdatadir, timerange=timerange
)
indicators1 = {"ema10": {}} indicators1 = {"ema10": {}}
indicators2 = {"macd": {"color": "red"}} indicators2 = {"macd": {"color": "red"}}
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# Generate entry/exit signals and indicators # Generate entry/exit signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair}) data = strategy.analyze_ticker(data, {"pair": pair})
fig = generate_empty_figure() fig = generate_empty_figure()
# Row 1 # Row 1
@ -99,39 +100,43 @@ def test_add_indicators(default_conf, testdatadir, caplog):
assert macd.line.color == "red" assert macd.line.color == "red"
# No indicator found # No indicator found
fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators={'no_indicator': {}}, data=data) fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators={"no_indicator": {}}, data=data)
assert fig == fig3 assert fig == fig3
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog) assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
def test_add_areas(default_conf, testdatadir, caplog): def test_add_areas(default_conf, testdatadir, caplog):
pair = "UNITTEST/BTC" pair = "UNITTEST/BTC"
timerange = TimeRange(None, 'line', 0, -1000) timerange = TimeRange(None, "line", 0, -1000)
data = history.load_pair_history(pair=pair, timeframe='1m', data = history.load_pair_history(
datadir=testdatadir, timerange=timerange) pair=pair, timeframe="1m", datadir=testdatadir, timerange=timerange
indicators = {"macd": {"color": "red", )
"fill_color": "black", indicators = {
"fill_to": "macdhist", "macd": {
"fill_label": "MACD Fill"}} "color": "red",
"fill_color": "black",
"fill_to": "macdhist",
"fill_label": "MACD Fill",
}
}
ind_no_label = {"macd": {"fill_color": "red", ind_no_label = {"macd": {"fill_color": "red", "fill_to": "macdhist"}}
"fill_to": "macdhist"}}
ind_plain = {"macd": {"fill_to": "macdhist"}} ind_plain = {"macd": {"fill_to": "macdhist"}}
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# Generate entry/exit signals and indicators # Generate entry/exit signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair}) data = strategy.analyze_ticker(data, {"pair": pair})
fig = generate_empty_figure() fig = generate_empty_figure()
# indicator mentioned in fill_to does not exist # indicator mentioned in fill_to does not exist
fig1 = add_areas(fig, 1, data, {'ema10': {'fill_to': 'no_fill_indicator'}}) fig1 = add_areas(fig, 1, data, {"ema10": {"fill_to": "no_fill_indicator"}})
assert fig == fig1 assert fig == fig1
assert log_has_re(r'fill_to: "no_fill_indicator" ignored\..*', caplog) assert log_has_re(r'fill_to: "no_fill_indicator" ignored\..*', caplog)
# indicator does not exist # indicator does not exist
fig2 = add_areas(fig, 1, data, {'no_indicator': {'fill_to': 'ema10'}}) fig2 = add_areas(fig, 1, data, {"no_indicator": {"fill_to": "ema10"}})
assert fig == fig2 assert fig == fig2
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog) assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
@ -168,56 +173,60 @@ def test_plot_trades(testdatadir, caplog):
pair = "ADA/BTC" pair = "ADA/BTC"
filename = testdatadir / "backtest_results/backtest-result.json" filename = testdatadir / "backtest_results/backtest-result.json"
trades = load_backtest_data(filename) trades = load_backtest_data(filename)
trades = trades.loc[trades['pair'] == pair] trades = trades.loc[trades["pair"] == pair]
fig = plot_trades(fig, trades) fig = plot_trades(fig, trades)
figure = fig1.layout.figure figure = fig1.layout.figure
# Check entry - color, should be in first graph, ... # Check entry - color, should be in first graph, ...
trade_entries = find_trace_in_fig_data(figure.data, 'Trade entry') trade_entries = find_trace_in_fig_data(figure.data, "Trade entry")
assert isinstance(trade_entries, go.Scatter) assert isinstance(trade_entries, go.Scatter)
assert trade_entries.yaxis == 'y' assert trade_entries.yaxis == "y"
assert len(trades) == len(trade_entries.x) assert len(trades) == len(trade_entries.x)
assert trade_entries.marker.color == 'cyan' assert trade_entries.marker.color == "cyan"
assert trade_entries.marker.symbol == 'circle-open' assert trade_entries.marker.symbol == "circle-open"
assert trade_entries.text[0] == '3.99%, buy_tag, roi, 15 min' assert trade_entries.text[0] == "3.99%, buy_tag, roi, 15 min"
trade_exit = find_trace_in_fig_data(figure.data, 'Exit - Profit') trade_exit = find_trace_in_fig_data(figure.data, "Exit - Profit")
assert isinstance(trade_exit, go.Scatter) assert isinstance(trade_exit, go.Scatter)
assert trade_exit.yaxis == 'y' assert trade_exit.yaxis == "y"
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_exit.x) assert len(trades.loc[trades["profit_ratio"] > 0]) == len(trade_exit.x)
assert trade_exit.marker.color == 'green' assert trade_exit.marker.color == "green"
assert trade_exit.marker.symbol == 'square-open' assert trade_exit.marker.symbol == "square-open"
assert trade_exit.text[0] == '3.99%, buy_tag, roi, 15 min' assert trade_exit.text[0] == "3.99%, buy_tag, roi, 15 min"
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Exit - Loss') trade_sell_loss = find_trace_in_fig_data(figure.data, "Exit - Loss")
assert isinstance(trade_sell_loss, go.Scatter) assert isinstance(trade_sell_loss, go.Scatter)
assert trade_sell_loss.yaxis == 'y' assert trade_sell_loss.yaxis == "y"
assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x) assert len(trades.loc[trades["profit_ratio"] <= 0]) == len(trade_sell_loss.x)
assert trade_sell_loss.marker.color == 'red' assert trade_sell_loss.marker.color == "red"
assert trade_sell_loss.marker.symbol == 'square-open' assert trade_sell_loss.marker.symbol == "square-open"
assert trade_sell_loss.text[5] == '-10.45%, stop_loss, 720 min' assert trade_sell_loss.text[5] == "-10.45%, stop_loss, 720 min"
def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog): def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog):
row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators', row_mock = mocker.patch(
MagicMock(side_effect=fig_generating_mock)) "freqtrade.plot.plotting.add_indicators", MagicMock(side_effect=fig_generating_mock)
trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades', )
MagicMock(side_effect=fig_generating_mock)) trades_mock = mocker.patch(
"freqtrade.plot.plotting.plot_trades", MagicMock(side_effect=fig_generating_mock)
)
pair = "UNITTEST/BTC" pair = "UNITTEST/BTC"
timerange = TimeRange(None, 'line', 0, -1000) timerange = TimeRange(None, "line", 0, -1000)
data = history.load_pair_history(pair=pair, timeframe='1m', data = history.load_pair_history(
datadir=testdatadir, timerange=timerange) pair=pair, timeframe="1m", datadir=testdatadir, timerange=timerange
data['enter_long'] = 0 )
data['exit_long'] = 0 data["enter_long"] = 0
data['enter_short'] = 0 data["exit_long"] = 0
data['exit_short'] = 0 data["enter_short"] = 0
data["exit_short"] = 0
indicators1 = [] indicators1 = []
indicators2 = [] indicators2 = []
fig = generate_candlestick_graph(pair=pair, data=data, trades=None, fig = generate_candlestick_graph(
indicators1=indicators1, indicators2=indicators2) pair=pair, data=data, trades=None, indicators1=indicators1, indicators2=indicators2
)
assert isinstance(fig, go.Figure) assert isinstance(fig, go.Figure)
assert fig.layout.title.text == pair assert fig.layout.title.text == pair
figure = fig.layout.figure figure = fig.layout.figure
@ -240,24 +249,28 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir): def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir):
row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators', row_mock = mocker.patch(
MagicMock(side_effect=fig_generating_mock)) "freqtrade.plot.plotting.add_indicators", MagicMock(side_effect=fig_generating_mock)
trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades', )
MagicMock(side_effect=fig_generating_mock)) trades_mock = mocker.patch(
pair = 'UNITTEST/BTC' "freqtrade.plot.plotting.plot_trades", MagicMock(side_effect=fig_generating_mock)
timerange = TimeRange(None, 'line', 0, -1000) )
data = history.load_pair_history(pair=pair, timeframe='1m', pair = "UNITTEST/BTC"
datadir=testdatadir, timerange=timerange) timerange = TimeRange(None, "line", 0, -1000)
data = history.load_pair_history(
pair=pair, timeframe="1m", datadir=testdatadir, timerange=timerange
)
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# Generate buy/sell signals and indicators # Generate buy/sell signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair}) data = strategy.analyze_ticker(data, {"pair": pair})
indicators1 = [] indicators1 = []
indicators2 = [] indicators2 = []
fig = generate_candlestick_graph(pair=pair, data=data, trades=None, fig = generate_candlestick_graph(
indicators1=indicators1, indicators2=indicators2) pair=pair, data=data, trades=None, indicators1=indicators1, indicators2=indicators2
)
assert isinstance(fig, go.Figure) assert isinstance(fig, go.Figure)
assert fig.layout.title.text == pair assert fig.layout.title.text == pair
figure = fig.layout.figure figure = fig.layout.figure
@ -273,12 +286,12 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
enter_long = find_trace_in_fig_data(figure.data, "enter_long") enter_long = find_trace_in_fig_data(figure.data, "enter_long")
assert isinstance(enter_long, go.Scatter) assert isinstance(enter_long, go.Scatter)
# All buy-signals should be plotted # All buy-signals should be plotted
assert int(data['enter_long'].sum()) == len(enter_long.x) assert int(data["enter_long"].sum()) == len(enter_long.x)
exit_long = find_trace_in_fig_data(figure.data, "exit_long") exit_long = find_trace_in_fig_data(figure.data, "exit_long")
assert isinstance(exit_long, go.Scatter) assert isinstance(exit_long, go.Scatter)
# All buy-signals should be plotted # All buy-signals should be plotted
assert int(data['exit_long'].sum()) == len(exit_long.x) assert int(data["exit_long"].sum()) == len(exit_long.x)
assert find_trace_in_fig_data(figure.data, "Bollinger Band") assert find_trace_in_fig_data(figure.data, "Bollinger Band")
@ -294,16 +307,15 @@ def test_generate_Plot_filename():
def test_generate_plot_file(mocker, caplog, user_dir): def test_generate_plot_file(mocker, caplog, user_dir):
fig = generate_empty_figure() fig = generate_empty_figure()
plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock()) plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock())
store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html", store_plot_file(
directory=user_dir / "plot") fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html", directory=user_dir / "plot"
)
expected_fn = str(user_dir / "plot/freqtrade-plot-UNITTEST_BTC-5m.html") expected_fn = str(user_dir / "plot/freqtrade-plot-UNITTEST_BTC-5m.html")
assert plot_mock.call_count == 1 assert plot_mock.call_count == 1
assert plot_mock.call_args[0][0] == fig assert plot_mock.call_args[0][0] == fig
assert (plot_mock.call_args_list[0][1]['filename'] assert plot_mock.call_args_list[0][1]["filename"] == expected_fn
== expected_fn) assert log_has(f"Stored plot as {expected_fn}", caplog)
assert log_has(f"Stored plot as {expected_fn}",
caplog)
def test_add_profit(testdatadir): def test_add_profit(testdatadir):
@ -311,15 +323,16 @@ def test_add_profit(testdatadir):
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
df = history.load_pair_history(pair="TRX/BTC", timeframe='5m', df = history.load_pair_history(
datadir=testdatadir, timerange=timerange) pair="TRX/BTC", timeframe="5m", datadir=testdatadir, timerange=timerange
)
fig = generate_empty_figure() fig = generate_empty_figure()
cum_profits = create_cum_profit(df.set_index('date'), cum_profits = create_cum_profit(
bt_data[bt_data["pair"] == 'TRX/BTC'], df.set_index("date"), bt_data[bt_data["pair"] == "TRX/BTC"], "cum_profits", timeframe="5m"
"cum_profits", timeframe="5m") )
fig1 = add_profit(fig, row=2, data=cum_profits, column='cum_profits', name='Profits') fig1 = add_profit(fig, row=2, data=cum_profits, column="cum_profits", name="Profits")
figure = fig1.layout.figure figure = fig1.layout.figure
profits = find_trace_in_fig_data(figure.data, "Profits") profits = find_trace_in_fig_data(figure.data, "Profits")
assert isinstance(profits, go.Scatter) assert isinstance(profits, go.Scatter)
@ -331,22 +344,15 @@ def test_generate_profit_graph(testdatadir):
trades = load_backtest_data(filename) trades = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
pairs = ["TRX/BTC", "XLM/BTC"] pairs = ["TRX/BTC", "XLM/BTC"]
trades = trades[trades['close_date'] < pd.Timestamp('2018-01-12', tz='UTC')] trades = trades[trades["close_date"] < pd.Timestamp("2018-01-12", tz="UTC")]
data = history.load_data(datadir=testdatadir, data = history.load_data(datadir=testdatadir, pairs=pairs, timeframe="5m", timerange=timerange)
pairs=pairs,
timeframe='5m',
timerange=timerange)
trades = trades[trades['pair'].isin(pairs)] trades = trades[trades["pair"].isin(pairs)]
fig = generate_profit_graph( fig = generate_profit_graph(
pairs, pairs, data, trades, timeframe="5m", stake_currency="BTC", starting_balance=0
data, )
trades,
timeframe="5m",
stake_currency='BTC',
starting_balance=0)
assert isinstance(fig, go.Figure) assert isinstance(fig, go.Figure)
assert fig.layout.title.text == "Freqtrade Profit plot" assert fig.layout.title.text == "Freqtrade Profit plot"
@ -379,40 +385,48 @@ def test_generate_profit_graph(testdatadir):
with pytest.raises(OperationalException, match=r"No trades found.*"): with pytest.raises(OperationalException, match=r"No trades found.*"):
# Pair cannot be empty - so it's an empty dataframe. # Pair cannot be empty - so it's an empty dataframe.
generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m", generate_profit_graph(
stake_currency='BTC', starting_balance=0) pairs,
data,
trades.loc[trades["pair"].isnull()],
timeframe="5m",
stake_currency="BTC",
starting_balance=0,
)
def test_start_plot_dataframe(mocker): def test_start_plot_dataframe(mocker):
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock()) aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
args = [ args = [
"plot-dataframe", "plot-dataframe",
"--config", "tests/testdata/testconfigs/main_test_config.json", "--config",
"--pairs", "ETH/BTC" "tests/testdata/testconfigs/main_test_config.json",
"--pairs",
"ETH/BTC",
] ]
start_plot_dataframe(get_args(args)) start_plot_dataframe(get_args(args))
assert aup.call_count == 1 assert aup.call_count == 1
called_config = aup.call_args_list[0][0][0] called_config = aup.call_args_list[0][0][0]
assert "pairs" in called_config assert "pairs" in called_config
assert called_config['pairs'] == ["ETH/BTC"] assert called_config["pairs"] == ["ETH/BTC"]
def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir): def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker) patch_exchange(mocker)
default_conf['trade_source'] = 'file' default_conf["trade_source"] = "file"
default_conf['exportfilename'] = testdatadir / "backtest-result.json" default_conf["exportfilename"] = testdatadir / "backtest-result.json"
default_conf['indicators1'] = ["sma5", "ema10"] default_conf["indicators1"] = ["sma5", "ema10"]
default_conf['indicators2'] = ["macd"] default_conf["indicators2"] = ["macd"]
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"] default_conf["pairs"] = ["ETH/BTC", "LTC/BTC"]
candle_mock = MagicMock() candle_mock = MagicMock()
store_mock = MagicMock() store_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.plot.plotting", "freqtrade.plot.plotting",
generate_candlestick_graph=candle_mock, generate_candlestick_graph=candle_mock,
store_plot_file=store_mock store_plot_file=store_mock,
) )
load_and_plot_trades(default_conf) load_and_plot_trades(default_conf)
@ -420,8 +434,8 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
assert candle_mock.call_count == 2 assert candle_mock.call_count == 2
assert store_mock.call_count == 2 assert store_mock.call_count == 2
assert candle_mock.call_args_list[0][1]['indicators1'] == ['sma5', 'ema10'] assert candle_mock.call_args_list[0][1]["indicators1"] == ["sma5", "ema10"]
assert candle_mock.call_args_list[0][1]['indicators2'] == ['macd'] assert candle_mock.call_args_list[0][1]["indicators2"] == ["macd"]
assert log_has("End of plotting process. 2 plots generated", caplog) assert log_has("End of plotting process. 2 plots generated", caplog)
@ -430,49 +444,46 @@ def test_start_plot_profit(mocker):
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock()) aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
args = [ args = [
"plot-profit", "plot-profit",
"--config", "tests/testdata/testconfigs/main_test_config.json", "--config",
"--pairs", "ETH/BTC" "tests/testdata/testconfigs/main_test_config.json",
"--pairs",
"ETH/BTC",
] ]
start_plot_profit(get_args(args)) start_plot_profit(get_args(args))
assert aup.call_count == 1 assert aup.call_count == 1
called_config = aup.call_args_list[0][0][0] called_config = aup.call_args_list[0][0][0]
assert "pairs" in called_config assert "pairs" in called_config
assert called_config['pairs'] == ["ETH/BTC"] assert called_config["pairs"] == ["ETH/BTC"]
def test_start_plot_profit_error(mocker): def test_start_plot_profit_error(mocker):
args = ["plot-profit", "--pairs", "ETH/BTC"]
args = [
'plot-profit',
'--pairs', 'ETH/BTC'
]
argsp = get_args(args) argsp = get_args(args)
# Make sure we use no config. Details: #2241 # Make sure we use no config. Details: #2241
# not resetting config causes random failures if config.json exists # not resetting config causes random failures if config.json exists
argsp['config'] = [] argsp["config"] = []
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
start_plot_profit(argsp) start_plot_profit(argsp)
def test_plot_profit(default_conf, mocker, testdatadir): def test_plot_profit(default_conf, mocker, testdatadir):
patch_exchange(mocker) patch_exchange(mocker)
default_conf['trade_source'] = 'file' default_conf["trade_source"] = "file"
default_conf['exportfilename'] = testdatadir / 'backtest-result_test_nofile.json' default_conf["exportfilename"] = testdatadir / "backtest-result_test_nofile.json"
default_conf['pairs'] = ['ETH/BTC', 'LTC/BTC'] default_conf["pairs"] = ["ETH/BTC", "LTC/BTC"]
profit_mock = MagicMock() profit_mock = MagicMock()
store_mock = MagicMock() store_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.plot.plotting", "freqtrade.plot.plotting", generate_profit_graph=profit_mock, store_plot_file=store_mock
generate_profit_graph=profit_mock,
store_plot_file=store_mock
) )
with pytest.raises(OperationalException, with pytest.raises(
match=r"No trades found, cannot generate Profit-plot.*"): OperationalException, match=r"No trades found, cannot generate Profit-plot.*"
):
plot_profit(default_conf) plot_profit(default_conf)
default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result.json" default_conf["exportfilename"] = testdatadir / "backtest_results/backtest-result.json"
plot_profit(default_conf) plot_profit(default_conf)
@ -480,53 +491,78 @@ def test_plot_profit(default_conf, mocker, testdatadir):
assert profit_mock.call_count == 1 assert profit_mock.call_count == 1
assert store_mock.call_count == 1 assert store_mock.call_count == 1
assert profit_mock.call_args_list[0][0][0] == default_conf['pairs'] assert profit_mock.call_args_list[0][0][0] == default_conf["pairs"]
assert store_mock.call_args_list[0][1]['auto_open'] is False assert store_mock.call_args_list[0][1]["auto_open"] is False
del default_conf['timeframe'] del default_conf["timeframe"]
with pytest.raises(OperationalException, match=r"Timeframe must be set.*--timeframe.*"): with pytest.raises(OperationalException, match=r"Timeframe must be set.*--timeframe.*"):
plot_profit(default_conf) plot_profit(default_conf)
@pytest.mark.parametrize("ind1,ind2,plot_conf,exp", [ @pytest.mark.parametrize(
# No indicators, use plot_conf "ind1,ind2,plot_conf,exp",
([], [], {}, [
{'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}}, # No indicators, use plot_conf
'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}}), (
# use indicators [],
(['sma', 'ema3'], ['macd'], {}, [],
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}), {},
# only main_plot - adds empty subplots {
([], [], {'main_plot': {'sma': {}}}, "main_plot": {"sma": {}, "ema3": {}, "ema5": {}},
{'main_plot': {'sma': {}}, 'subplots': {}}), "subplots": {"Other": {"macd": {}, "macdsignal": {}}},
# Main and subplots },
([], [], {'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, ),
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}), # use indicators
# no main_plot, adds empty main_plot (
([], [], {'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, ["sma", "ema3"],
{'main_plot': {}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}), ["macd"],
# indicator 1 / 2 should have prevalence {},
(['sma', 'ema3'], ['macd'], {"main_plot": {"sma": {}, "ema3": {}}, "subplots": {"Other": {"macd": {}}}},
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, ),
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}} # only main_plot - adds empty subplots
), ([], [], {"main_plot": {"sma": {}}}, {"main_plot": {"sma": {}}, "subplots": {}}),
# indicator 1 - overrides plot_config main_plot # Main and subplots
(['sma', 'ema3'], [], (
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, [],
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}} [],
), {"main_plot": {"sma": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
# indicator 2 - overrides plot_config subplots {"main_plot": {"sma": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
([], ['macd', 'macd_signal'], ),
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, # no main_plot, adds empty main_plot
{'main_plot': {'sma': {}}, 'subplots': {'Other': {'macd': {}, 'macd_signal': {}}}} (
), [],
]) [],
{"subplots": {"RSI": {"rsi": {"color": "red"}}}},
{"main_plot": {}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
),
# indicator 1 / 2 should have prevalence
(
["sma", "ema3"],
["macd"],
{"main_plot": {"sma": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
{"main_plot": {"sma": {}, "ema3": {}}, "subplots": {"Other": {"macd": {}}}},
),
# indicator 1 - overrides plot_config main_plot
(
["sma", "ema3"],
[],
{"main_plot": {"sma": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
{"main_plot": {"sma": {}, "ema3": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
),
# indicator 2 - overrides plot_config subplots
(
[],
["macd", "macd_signal"],
{"main_plot": {"sma": {}}, "subplots": {"RSI": {"rsi": {"color": "red"}}}},
{"main_plot": {"sma": {}}, "subplots": {"Other": {"macd": {}, "macd_signal": {}}}},
),
],
)
def test_create_plotconfig(ind1, ind2, plot_conf, exp): def test_create_plotconfig(ind1, ind2, plot_conf, exp):
res = create_plotconfig(ind1, ind2, plot_conf) res = create_plotconfig(ind1, ind2, plot_conf)
assert 'main_plot' in res assert "main_plot" in res
assert 'subplots' in res assert "subplots" in res
assert isinstance(res['main_plot'], dict) assert isinstance(res["main_plot"], dict)
assert isinstance(res['subplots'], dict) assert isinstance(res["subplots"], dict)
assert res == exp assert res == exp

View File

@ -3,12 +3,14 @@ import talib.abstract as ta
def test_talib_bollingerbands_near_zero_values(): def test_talib_bollingerbands_near_zero_values():
inputs = pd.DataFrame([ inputs = pd.DataFrame(
{'close': 0.00000010}, [
{'close': 0.00000011}, {"close": 0.00000010},
{'close': 0.00000012}, {"close": 0.00000011},
{'close': 0.00000013}, {"close": 0.00000012},
{'close': 0.00000014} {"close": 0.00000013},
]) {"close": 0.00000014},
]
)
bollinger = ta.BBANDS(inputs, matype=0, timeperiod=2) bollinger = ta.BBANDS(inputs, matype=0, timeperiod=2)
assert bollinger['upperband'][3] != bollinger['middleband'][3] assert bollinger["upperband"][3] != bollinger["middleband"][3]

View File

@ -18,185 +18,179 @@ from tests.conftest import (
def test_sync_wallet_at_boot(mocker, default_conf): def test_sync_wallet_at_boot(mocker, default_conf):
default_conf['dry_run'] = False default_conf["dry_run"] = False
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
get_balances=MagicMock(return_value={ get_balances=MagicMock(
"BNT": { return_value={
"free": 1.0, "BNT": {"free": 1.0, "used": 2.0, "total": 3.0},
"used": 2.0, "GAS": {"free": 0.260739, "used": 0.0, "total": 0.260739},
"total": 3.0 "USDT": {"free": 20, "used": 20, "total": 40},
}, }
"GAS": { ),
"free": 0.260739,
"used": 0.0,
"total": 0.260739
},
"USDT": {
"free": 20,
"used": 20,
"total": 40
},
})
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 3 assert len(freqtrade.wallets._wallets) == 3
assert freqtrade.wallets._wallets['BNT'].free == 1.0 assert freqtrade.wallets._wallets["BNT"].free == 1.0
assert freqtrade.wallets._wallets['BNT'].used == 2.0 assert freqtrade.wallets._wallets["BNT"].used == 2.0
assert freqtrade.wallets._wallets['BNT'].total == 3.0 assert freqtrade.wallets._wallets["BNT"].total == 3.0
assert freqtrade.wallets._wallets['GAS'].free == 0.260739 assert freqtrade.wallets._wallets["GAS"].free == 0.260739
assert freqtrade.wallets._wallets['GAS'].used == 0.0 assert freqtrade.wallets._wallets["GAS"].used == 0.0
assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets._wallets["GAS"].total == 0.260739
assert freqtrade.wallets.get_free('BNT') == 1.0 assert freqtrade.wallets.get_free("BNT") == 1.0
assert 'USDT' in freqtrade.wallets._wallets assert "USDT" in freqtrade.wallets._wallets
assert freqtrade.wallets._last_wallet_refresh is not None assert freqtrade.wallets._last_wallet_refresh is not None
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
get_balances=MagicMock(return_value={ get_balances=MagicMock(
"BNT": { return_value={
"free": 1.2, "BNT": {"free": 1.2, "used": 1.9, "total": 3.5},
"used": 1.9, "GAS": {"free": 0.270739, "used": 0.1, "total": 0.260439},
"total": 3.5 }
}, ),
"GAS": {
"free": 0.270739,
"used": 0.1,
"total": 0.260439
},
})
) )
freqtrade.wallets.update() freqtrade.wallets.update()
# USDT is missing from the 2nd result - so should not be in this either. # USDT is missing from the 2nd result - so should not be in this either.
assert len(freqtrade.wallets._wallets) == 2 assert len(freqtrade.wallets._wallets) == 2
assert freqtrade.wallets._wallets['BNT'].free == 1.2 assert freqtrade.wallets._wallets["BNT"].free == 1.2
assert freqtrade.wallets._wallets['BNT'].used == 1.9 assert freqtrade.wallets._wallets["BNT"].used == 1.9
assert freqtrade.wallets._wallets['BNT'].total == 3.5 assert freqtrade.wallets._wallets["BNT"].total == 3.5
assert freqtrade.wallets._wallets['GAS'].free == 0.270739 assert freqtrade.wallets._wallets["GAS"].free == 0.270739
assert freqtrade.wallets._wallets['GAS'].used == 0.1 assert freqtrade.wallets._wallets["GAS"].used == 0.1
assert freqtrade.wallets._wallets['GAS'].total == 0.260439 assert freqtrade.wallets._wallets["GAS"].total == 0.260439
assert freqtrade.wallets.get_free('GAS') == 0.270739 assert freqtrade.wallets.get_free("GAS") == 0.270739
assert freqtrade.wallets.get_used('GAS') == 0.1 assert freqtrade.wallets.get_used("GAS") == 0.1
assert freqtrade.wallets.get_total('GAS') == 0.260439 assert freqtrade.wallets.get_total("GAS") == 0.260439
update_mock = mocker.patch('freqtrade.wallets.Wallets._update_live') update_mock = mocker.patch("freqtrade.wallets.Wallets._update_live")
freqtrade.wallets.update(False) freqtrade.wallets.update(False)
assert update_mock.call_count == 0 assert update_mock.call_count == 0
freqtrade.wallets.update() freqtrade.wallets.update()
assert update_mock.call_count == 1 assert update_mock.call_count == 1
assert freqtrade.wallets.get_free('NOCURRENCY') == 0 assert freqtrade.wallets.get_free("NOCURRENCY") == 0
assert freqtrade.wallets.get_used('NOCURRENCY') == 0 assert freqtrade.wallets.get_used("NOCURRENCY") == 0
assert freqtrade.wallets.get_total('NOCURRENCY') == 0 assert freqtrade.wallets.get_total("NOCURRENCY") == 0
def test_sync_wallet_missing_data(mocker, default_conf): def test_sync_wallet_missing_data(mocker, default_conf):
default_conf['dry_run'] = False default_conf["dry_run"] = False
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
get_balances=MagicMock(return_value={ get_balances=MagicMock(
"BNT": { return_value={
"free": 1.0, "BNT": {"free": 1.0, "used": 2.0, "total": 3.0},
"used": 2.0, "GAS": {"free": 0.260739, "total": 0.260739},
"total": 3.0 }
}, ),
"GAS": {
"free": 0.260739,
"total": 0.260739
},
})
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 2 assert len(freqtrade.wallets._wallets) == 2
assert freqtrade.wallets._wallets['BNT'].free == 1.0 assert freqtrade.wallets._wallets["BNT"].free == 1.0
assert freqtrade.wallets._wallets['BNT'].used == 2.0 assert freqtrade.wallets._wallets["BNT"].used == 2.0
assert freqtrade.wallets._wallets['BNT'].total == 3.0 assert freqtrade.wallets._wallets["BNT"].total == 3.0
assert freqtrade.wallets._wallets['GAS'].free == 0.260739 assert freqtrade.wallets._wallets["GAS"].free == 0.260739
assert freqtrade.wallets._wallets['GAS'].used is None assert freqtrade.wallets._wallets["GAS"].used is None
assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets._wallets["GAS"].total == 0.260739
assert freqtrade.wallets.get_free('GAS') == 0.260739 assert freqtrade.wallets.get_free("GAS") == 0.260739
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None: def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) patch_wallet(mocker, free=default_conf["stake_amount"] * 0.5)
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
with pytest.raises(DependencyException, match=r'.*stake amount.*'): with pytest.raises(DependencyException, match=r".*stake amount.*"):
freqtrade.wallets.get_trade_stake_amount('ETH/BTC', 1) freqtrade.wallets.get_trade_stake_amount("ETH/BTC", 1)
@pytest.mark.parametrize("balance_ratio,capital,result1,result2", [ @pytest.mark.parametrize(
(1, None, 50, 66.66666), "balance_ratio,capital,result1,result2",
(0.99, None, 49.5, 66.0), [
(0.50, None, 25, 33.3333), (1, None, 50, 66.66666),
# Tests with capital ignore balance_ratio (0.99, None, 49.5, 66.0),
(1, 100, 50, 0.0), (0.50, None, 25, 33.3333),
(0.99, 200, 50, 66.66666), # Tests with capital ignore balance_ratio
(0.99, 150, 50, 50), (1, 100, 50, 0.0),
(0.50, 50, 25, 0.0), (0.99, 200, 50, 66.66666),
(0.50, 10, 5, 0.0), (0.99, 150, 50, 50),
]) (0.50, 50, 25, 0.0),
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, capital, (0.50, 10, 5, 0.0),
result1, result2, limit_buy_order_open, ],
fee, mocker) -> None: )
def test_get_trade_stake_amount_unlimited_amount(
default_conf,
ticker,
balance_ratio,
capital,
result1,
result2,
limit_buy_order_open,
fee,
mocker,
) -> None:
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
fetch_ticker=ticker, fetch_ticker=ticker,
create_order=MagicMock(return_value=limit_buy_order_open), create_order=MagicMock(return_value=limit_buy_order_open),
get_fee=fee get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT conf["stake_amount"] = UNLIMITED_STAKE_AMOUNT
conf['dry_run_wallet'] = 100 conf["dry_run_wallet"] = 100
conf['tradable_balance_ratio'] = balance_ratio conf["tradable_balance_ratio"] = balance_ratio
if capital is not None: if capital is not None:
conf['available_capital'] = capital conf["available_capital"] = capital
freqtrade = get_patched_freqtradebot(mocker, conf) freqtrade = get_patched_freqtradebot(mocker, conf)
# no open trades, order amount should be 'balance / max_open_trades' # no open trades, order amount should be 'balance / max_open_trades'
result = freqtrade.wallets.get_trade_stake_amount('ETH/USDT', 2) result = freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 2)
assert result == result1 assert result == result1
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
freqtrade.execute_entry('ETH/USDT', result) freqtrade.execute_entry("ETH/USDT", result)
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT', 2) result = freqtrade.wallets.get_trade_stake_amount("LTC/USDT", 2)
assert result == result1 assert result == result1
# create 2 trades, order amount should be None # create 2 trades, order amount should be None
freqtrade.execute_entry('LTC/BTC', result) freqtrade.execute_entry("LTC/BTC", result)
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT', 2) result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 2)
assert result == 0 assert result == 0
freqtrade.config['dry_run_wallet'] = 200 freqtrade.config["dry_run_wallet"] = 200
freqtrade.wallets.start_cap = 200 freqtrade.wallets.start_cap = 200
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT', 3) result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 3)
assert round(result, 4) == round(result2, 4) assert round(result, 4) == round(result2, 4)
# set max_open_trades = None, so do not trade # set max_open_trades = None, so do not trade
result = freqtrade.wallets.get_trade_stake_amount('NEO/USDT', 0) result = freqtrade.wallets.get_trade_stake_amount("NEO/USDT", 0)
assert result == 0 assert result == 0
@pytest.mark.parametrize('stake_amount,min_stake,stake_available,max_stake,trade_amount,expected', [ @pytest.mark.parametrize(
(22, 11, 50, 10000, None, 22), "stake_amount,min_stake,stake_available,max_stake,trade_amount,expected",
(100, 11, 500, 10000, None, 100), [
(1000, 11, 500, 10000, None, 500), # Above stake_available (22, 11, 50, 10000, None, 22),
(700, 11, 1000, 400, None, 400), # Above max_stake, below stake available (100, 11, 500, 10000, None, 100),
(20, 15, 10, 10000, None, 0), # Minimum stake > stake_available (1000, 11, 500, 10000, None, 500), # Above stake_available
(9, 11, 100, 10000, None, 11), # Below min stake (700, 11, 1000, 400, None, 400), # Above max_stake, below stake available
(1, 15, 10, 10000, None, 0), # Below min stake and min_stake > stake_available (20, 15, 10, 10000, None, 0), # Minimum stake > stake_available
(20, 50, 100, 10000, None, 0), # Below min stake and stake * 1.3 > min_stake (9, 11, 100, 10000, None, 11), # Below min stake
(1000, None, 1000, 10000, None, 1000), # No min-stake-amount could be determined (1, 15, 10, 10000, None, 0), # Below min stake and min_stake > stake_available
(2000, 15, 2000, 3000, 1500, 1500), # Rebuy - resulting in too high stake amount. Adjusting. (20, 50, 100, 10000, None, 0), # Below min stake and stake * 1.3 > min_stake
]) (1000, None, 1000, 10000, None, 1000), # No min-stake-amount could be determined
# Rebuy - resulting in too high stake amount. Adjusting.
(2000, 15, 2000, 3000, 1500, 1500),
],
)
def test_validate_stake_amount( def test_validate_stake_amount(
mocker, mocker,
default_conf, default_conf,
@ -209,33 +203,41 @@ def test_validate_stake_amount(
): ):
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount", mocker.patch(
return_value=stake_available) "freqtrade.wallets.Wallets.get_available_stake_amount", return_value=stake_available
)
res = freqtrade.wallets.validate_stake_amount( res = freqtrade.wallets.validate_stake_amount(
'XRP/USDT', stake_amount, min_stake, max_stake, trade_amount) "XRP/USDT", stake_amount, min_stake, max_stake, trade_amount
)
assert res == expected assert res == expected
@pytest.mark.parametrize('available_capital,closed_profit,open_stakes,free,expected', [ @pytest.mark.parametrize(
(None, 10, 100, 910, 1000), "available_capital,closed_profit,open_stakes,free,expected",
(None, 0, 0, 2500, 2500), [
(None, 500, 0, 2500, 2000), (None, 10, 100, 910, 1000),
(None, 500, 0, 2500, 2000), (None, 0, 0, 2500, 2500),
(None, -70, 0, 1930, 2000), (None, 500, 0, 2500, 2000),
# Only available balance matters when it's set. (None, 500, 0, 2500, 2000),
(100, 0, 0, 0, 100), (None, -70, 0, 1930, 2000),
(1000, 0, 2, 5, 1000), # Only available balance matters when it's set.
(1235, 2250, 2, 5, 1235), (100, 0, 0, 0, 100),
(1235, -2250, 2, 5, 1235), (1000, 0, 2, 5, 1000),
]) (1235, 2250, 2, 5, 1235),
def test_get_starting_balance(mocker, default_conf, available_capital, closed_profit, (1235, -2250, 2, 5, 1235),
open_stakes, free, expected): ],
)
def test_get_starting_balance(
mocker, default_conf, available_capital, closed_profit, open_stakes, free, expected
):
if available_capital: if available_capital:
default_conf['available_capital'] = available_capital default_conf["available_capital"] = available_capital
mocker.patch("freqtrade.persistence.models.Trade.get_total_closed_profit", mocker.patch(
return_value=closed_profit) "freqtrade.persistence.models.Trade.get_total_closed_profit", return_value=closed_profit
mocker.patch("freqtrade.persistence.models.Trade.total_open_trades_stakes", )
return_value=open_stakes) mocker.patch(
"freqtrade.persistence.models.Trade.total_open_trades_stakes", return_value=open_stakes
)
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=free) mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=free)
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
@ -244,9 +246,9 @@ def test_get_starting_balance(mocker, default_conf, available_capital, closed_pr
def test_sync_wallet_futures_live(mocker, default_conf): def test_sync_wallet_futures_live(mocker, default_conf):
default_conf['dry_run'] = False default_conf["dry_run"] = False
default_conf['trading_mode'] = 'futures' default_conf["trading_mode"] = "futures"
default_conf['margin_mode'] = 'isolated' default_conf["margin_mode"] = "isolated"
mock_result = [ mock_result = [
{ {
"symbol": "ETH/USDT:USDT", "symbol": "ETH/USDT:USDT",
@ -267,8 +269,8 @@ def test_sync_wallet_futures_live(mocker, default_conf):
"markPrice": 2896.41, "markPrice": 2896.41,
"collateral": 20, "collateral": 20,
"marginType": "isolated", "marginType": "isolated",
"side": 'short', "side": "short",
"percentage": None "percentage": None,
}, },
{ {
"symbol": "ADA/USDT:USDT", "symbol": "ADA/USDT:USDT",
@ -289,8 +291,8 @@ def test_sync_wallet_futures_live(mocker, default_conf):
"markPrice": 0.91, "markPrice": 0.91,
"collateral": 20, "collateral": 20,
"marginType": "isolated", "marginType": "isolated",
"side": 'short', "side": "short",
"percentage": None "percentage": None,
}, },
{ {
# Closed position # Closed position
@ -312,20 +314,18 @@ def test_sync_wallet_futures_live(mocker, default_conf):
"markPrice": 15.41, "markPrice": 15.41,
"collateral": 0.0, "collateral": 0.0,
"marginType": "isolated", "marginType": "isolated",
"side": 'short', "side": "short",
"percentage": None "percentage": None,
} },
] ]
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
get_balances=MagicMock(return_value={ get_balances=MagicMock(
"USDT": { return_value={
"free": 900, "USDT": {"free": 900, "used": 100, "total": 1000},
"used": 100, }
"total": 1000 ),
}, fetch_positions=MagicMock(return_value=mock_result),
}),
fetch_positions=MagicMock(return_value=mock_result)
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
@ -333,23 +333,23 @@ def test_sync_wallet_futures_live(mocker, default_conf):
assert len(freqtrade.wallets._wallets) == 1 assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 2 assert len(freqtrade.wallets._positions) == 2
assert 'USDT' in freqtrade.wallets._wallets assert "USDT" in freqtrade.wallets._wallets
assert 'ETH/USDT:USDT' in freqtrade.wallets._positions assert "ETH/USDT:USDT" in freqtrade.wallets._positions
assert freqtrade.wallets._last_wallet_refresh is not None assert freqtrade.wallets._last_wallet_refresh is not None
# Remove ETH/USDT:USDT position # Remove ETH/USDT:USDT position
del mock_result[0] del mock_result[0]
freqtrade.wallets.update() freqtrade.wallets.update()
assert len(freqtrade.wallets._positions) == 1 assert len(freqtrade.wallets._positions) == 1
assert 'ETH/USDT:USDT' not in freqtrade.wallets._positions assert "ETH/USDT:USDT" not in freqtrade.wallets._positions
def test_sync_wallet_dry(mocker, default_conf_usdt, fee): def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
default_conf_usdt['dry_run'] = True default_conf_usdt["dry_run"] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert len(freqtrade.wallets._wallets) == 1 assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 0 assert len(freqtrade.wallets._positions) == 0
assert freqtrade.wallets.get_total('USDT') == 1000 assert freqtrade.wallets.get_total("USDT") == 1000
create_mock_trades_usdt(fee, is_short=None) create_mock_trades_usdt(fee, is_short=None)
@ -358,23 +358,23 @@ def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
assert len(freqtrade.wallets._wallets) == 5 assert len(freqtrade.wallets._wallets) == 5
assert len(freqtrade.wallets._positions) == 0 assert len(freqtrade.wallets._positions) == 0
bal = freqtrade.wallets.get_all_balances() bal = freqtrade.wallets.get_all_balances()
assert bal['NEO'].total == 10 assert bal["NEO"].total == 10
assert bal['XRP'].total == 10 assert bal["XRP"].total == 10
assert bal['LTC'].total == 2 assert bal["LTC"].total == 2
assert bal['USDT'].total == 922.74 assert bal["USDT"].total == 922.74
assert freqtrade.wallets.get_starting_balance() == default_conf_usdt['dry_run_wallet'] assert freqtrade.wallets.get_starting_balance() == default_conf_usdt["dry_run_wallet"]
total = freqtrade.wallets.get_total('LTC') total = freqtrade.wallets.get_total("LTC")
free = freqtrade.wallets.get_free('LTC') free = freqtrade.wallets.get_free("LTC")
used = freqtrade.wallets.get_used('LTC') used = freqtrade.wallets.get_used("LTC")
assert free != 0 assert free != 0
assert free + used == total assert free + used == total
def test_sync_wallet_futures_dry(mocker, default_conf, fee): def test_sync_wallet_futures_dry(mocker, default_conf, fee):
default_conf['dry_run'] = True default_conf["dry_run"] = True
default_conf['trading_mode'] = 'futures' default_conf["trading_mode"] = "futures"
default_conf['margin_mode'] = 'isolated' default_conf["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1 assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 0 assert len(freqtrade.wallets._positions) == 0
@ -386,15 +386,15 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee):
assert len(freqtrade.wallets._wallets) == 1 assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 4 assert len(freqtrade.wallets._positions) == 4
positions = freqtrade.wallets.get_all_positions() positions = freqtrade.wallets.get_all_positions()
assert positions['ETH/BTC'].side == 'short' assert positions["ETH/BTC"].side == "short"
assert positions['ETC/BTC'].side == 'long' assert positions["ETC/BTC"].side == "long"
assert positions['XRP/BTC'].side == 'long' assert positions["XRP/BTC"].side == "long"
assert positions['LTC/BTC'].side == 'short' assert positions["LTC/BTC"].side == "short"
assert freqtrade.wallets.get_starting_balance() == default_conf['dry_run_wallet'] assert freqtrade.wallets.get_starting_balance() == default_conf["dry_run_wallet"]
total = freqtrade.wallets.get_total('BTC') total = freqtrade.wallets.get_total("BTC")
free = freqtrade.wallets.get_free('BTC') free = freqtrade.wallets.get_free("BTC")
used = freqtrade.wallets.get_used('BTC') used = freqtrade.wallets.get_used("BTC")
assert free + used == total assert free + used == total
@ -421,14 +421,14 @@ def test_check_exit_amount(mocker, default_conf, fee):
def test_check_exit_amount_futures(mocker, default_conf, fee): def test_check_exit_amount_futures(mocker, default_conf, fee):
default_conf['trading_mode'] = 'futures' default_conf["trading_mode"] = "futures"
default_conf['margin_mode'] = 'isolated' default_conf["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123)
create_mock_trades(fee, is_short=None) create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first() trade = Trade.session.scalars(select(Trade)).first()
trade.trading_mode = 'futures' trade.trading_mode = "futures"
assert trade.amount == 123 assert trade.amount == 123
assert freqtrade.wallets.check_exit_amount(trade) is True assert freqtrade.wallets.check_exit_amount(trade) is True