mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 02:12:01 +00:00
ruff format: Update a few test files
This commit is contained in:
parent
baa15f6ed6
commit
7090950db6
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user