mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-12 19:23:55 +00:00
Compare commits
18 Commits
c2f08b1d1b
...
7fb36d2497
Author | SHA1 | Date | |
---|---|---|---|
|
7fb36d2497 | ||
|
af422c7cd4 | ||
|
51bdecea53 | ||
|
0f505c6d7b | ||
|
ae72f10448 | ||
|
ae155c78c2 | ||
|
f970454cb4 | ||
|
69678574d4 | ||
|
53cab5074b | ||
|
c6c65b1799 | ||
|
bb9f64027a | ||
|
5f52fc4338 | ||
|
82e30c8519 | ||
|
705d1e4cc0 | ||
|
5b3f348bbb | ||
|
aa81c75bef | ||
|
6b889814ad | ||
|
1ade11f00b |
|
@ -101,3 +101,4 @@ This could lead to a false-negative (the strategy will then be reported as non-b
|
||||||
- `lookahead-analysis` has access to everything that backtesting has too.
|
- `lookahead-analysis` has access to everything that backtesting has too.
|
||||||
Please don't provoke any configs like enabling position stacking.
|
Please don't provoke any configs like enabling position stacking.
|
||||||
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` amount and neither leftover money in your wallet.
|
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` amount and neither leftover money in your wallet.
|
||||||
|
- In the results table, the `biased_indicators` column will falsely flag FreqAI target indicators defined in `set_freqai_targets()` as biased. These are not biased and can safely be ignored.
|
||||||
|
|
|
@ -78,6 +78,7 @@ class Kraken(Exchange):
|
||||||
# x["side"], x["amount"],
|
# x["side"], x["amount"],
|
||||||
)
|
)
|
||||||
for x in orders
|
for x in orders
|
||||||
|
if x["remaining"] is not None and (x["side"] == "sell" or x["price"] is not None)
|
||||||
]
|
]
|
||||||
for bal in balances:
|
for bal in balances:
|
||||||
if not isinstance(balances[bal], dict):
|
if not isinstance(balances[bal], dict):
|
||||||
|
|
|
@ -47,19 +47,20 @@ class BaseEnvironment(gym.Env):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
df: DataFrame = DataFrame(),
|
*,
|
||||||
prices: DataFrame = DataFrame(),
|
df: DataFrame,
|
||||||
reward_kwargs: dict = {},
|
prices: DataFrame,
|
||||||
|
reward_kwargs: dict,
|
||||||
window_size=10,
|
window_size=10,
|
||||||
starting_point=True,
|
starting_point=True,
|
||||||
id: str = "baseenv-1", # noqa: A002
|
id: str = "baseenv-1", # noqa: A002
|
||||||
seed: int = 1,
|
seed: int = 1,
|
||||||
config: dict = {},
|
config: dict,
|
||||||
live: bool = False,
|
live: bool = False,
|
||||||
fee: float = 0.0015,
|
fee: float = 0.0015,
|
||||||
can_short: bool = False,
|
can_short: bool = False,
|
||||||
pair: str = "",
|
pair: str = "",
|
||||||
df_raw: DataFrame = DataFrame(),
|
df_raw: DataFrame,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initializes the training/eval environment.
|
Initializes the training/eval environment.
|
||||||
|
|
|
@ -488,7 +488,7 @@ def make_env(
|
||||||
seed: int,
|
seed: int,
|
||||||
train_df: DataFrame,
|
train_df: DataFrame,
|
||||||
price: DataFrame,
|
price: DataFrame,
|
||||||
env_info: Dict[str, Any] = {},
|
env_info: Dict[str, Any],
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
"""
|
"""
|
||||||
Utility function for multiprocessed env.
|
Utility function for multiprocessed env.
|
||||||
|
|
|
@ -214,7 +214,7 @@ class FreqaiDataKitchen:
|
||||||
self,
|
self,
|
||||||
unfiltered_df: DataFrame,
|
unfiltered_df: DataFrame,
|
||||||
training_feature_list: List,
|
training_feature_list: List,
|
||||||
label_list: List = list(),
|
label_list: Optional[List] = None,
|
||||||
training_filter: bool = True,
|
training_filter: bool = True,
|
||||||
) -> Tuple[DataFrame, DataFrame]:
|
) -> Tuple[DataFrame, DataFrame]:
|
||||||
"""
|
"""
|
||||||
|
@ -244,7 +244,7 @@ class FreqaiDataKitchen:
|
||||||
# we don't care about total row number (total no. datapoints) in training, we only care
|
# we don't care about total row number (total no. datapoints) in training, we only care
|
||||||
# about removing any row with NaNs
|
# about removing any row with NaNs
|
||||||
# if labels has multiple columns (user wants to train multiple modelEs), we detect here
|
# if labels has multiple columns (user wants to train multiple modelEs), we detect here
|
||||||
labels = unfiltered_df.filter(label_list, axis=1)
|
labels = unfiltered_df.filter(label_list or [], axis=1)
|
||||||
drop_index_labels = pd.isnull(labels).any(axis=1)
|
drop_index_labels = pd.isnull(labels).any(axis=1)
|
||||||
drop_index_labels = (
|
drop_index_labels = (
|
||||||
drop_index_labels.replace(True, 1).replace(False, 0).infer_objects(copy=False)
|
drop_index_labels.replace(True, 1).replace(False, 0).infer_objects(copy=False)
|
||||||
|
@ -654,8 +654,8 @@ class FreqaiDataKitchen:
|
||||||
pair: str,
|
pair: str,
|
||||||
tf: str,
|
tf: str,
|
||||||
strategy: IStrategy,
|
strategy: IStrategy,
|
||||||
corr_dataframes: dict = {},
|
corr_dataframes: dict,
|
||||||
base_dataframes: dict = {},
|
base_dataframes: dict,
|
||||||
is_corr_pairs: bool = False,
|
is_corr_pairs: bool = False,
|
||||||
) -> DataFrame:
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
|
@ -776,7 +776,7 @@ class FreqaiDataKitchen:
|
||||||
corr_dataframes: dict = {},
|
corr_dataframes: dict = {},
|
||||||
base_dataframes: dict = {},
|
base_dataframes: dict = {},
|
||||||
pair: str = "",
|
pair: str = "",
|
||||||
prediction_dataframe: DataFrame = pd.DataFrame(),
|
prediction_dataframe: Optional[DataFrame] = None,
|
||||||
do_corr_pairs: bool = True,
|
do_corr_pairs: bool = True,
|
||||||
) -> DataFrame:
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
|
@ -822,7 +822,7 @@ class FreqaiDataKitchen:
|
||||||
if tf not in corr_dataframes[p]:
|
if tf not in corr_dataframes[p]:
|
||||||
corr_dataframes[p][tf] = pd.DataFrame()
|
corr_dataframes[p][tf] = pd.DataFrame()
|
||||||
|
|
||||||
if not prediction_dataframe.empty:
|
if prediction_dataframe is not None and not prediction_dataframe.empty:
|
||||||
dataframe = prediction_dataframe.copy()
|
dataframe = prediction_dataframe.copy()
|
||||||
base_dataframes[self.config["timeframe"]] = dataframe.copy()
|
base_dataframes[self.config["timeframe"]] = dataframe.copy()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
|
||||||
criterion: nn.Module,
|
criterion: nn.Module,
|
||||||
device: str,
|
device: str,
|
||||||
data_convertor: PyTorchDataConvertor,
|
data_convertor: PyTorchDataConvertor,
|
||||||
model_meta_data: Dict[str, Any] = {},
|
model_meta_data: Optional[Dict[str, Any]] = None,
|
||||||
window_size: int = 1,
|
window_size: int = 1,
|
||||||
tb_logger: Any = None,
|
tb_logger: Any = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
@ -45,6 +45,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
|
||||||
:param n_epochs: The maximum number batches to use for evaluation.
|
:param n_epochs: The maximum number batches to use for evaluation.
|
||||||
:param batch_size: The size of the batches to use during training.
|
:param batch_size: The size of the batches to use during training.
|
||||||
"""
|
"""
|
||||||
|
if model_meta_data is None:
|
||||||
|
model_meta_data = {}
|
||||||
self.model = model
|
self.model = model
|
||||||
self.optimizer = optimizer
|
self.optimizer = optimizer
|
||||||
self.criterion = criterion
|
self.criterion = criterion
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
@ -19,7 +19,9 @@ logger = logging.getLogger(__name__)
|
||||||
class LookaheadAnalysisSubFunctions:
|
class LookaheadAnalysisSubFunctions:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def text_table_lookahead_analysis_instances(
|
def text_table_lookahead_analysis_instances(
|
||||||
config: Dict[str, Any], lookahead_instances: List[LookaheadAnalysis]
|
config: Dict[str, Any],
|
||||||
|
lookahead_instances: List[LookaheadAnalysis],
|
||||||
|
caption: Union[str, None] = None,
|
||||||
):
|
):
|
||||||
headers = [
|
headers = [
|
||||||
"filename",
|
"filename",
|
||||||
|
@ -65,7 +67,9 @@ class LookaheadAnalysisSubFunctions:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
print_rich_table(data, headers, summary="Lookahead Analysis")
|
print_rich_table(
|
||||||
|
data, headers, summary="Lookahead Analysis", table_kwargs={"caption": caption}
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -239,8 +243,24 @@ class LookaheadAnalysisSubFunctions:
|
||||||
|
|
||||||
# report the results
|
# report the results
|
||||||
if lookaheadAnalysis_instances:
|
if lookaheadAnalysis_instances:
|
||||||
|
caption: Union[str, None] = None
|
||||||
|
if any(
|
||||||
|
[
|
||||||
|
any(
|
||||||
|
[
|
||||||
|
indicator.startswith("&")
|
||||||
|
for indicator in inst.current_analysis.false_indicators
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for inst in lookaheadAnalysis_instances
|
||||||
|
]
|
||||||
|
):
|
||||||
|
caption = (
|
||||||
|
"Any indicators in 'biased_indicators' which are used within "
|
||||||
|
"set_freqai_targets() can be ignored."
|
||||||
|
)
|
||||||
LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||||
config, lookaheadAnalysis_instances
|
config, lookaheadAnalysis_instances, caption=caption
|
||||||
)
|
)
|
||||||
if config.get("lookahead_analysis_exportfilename") is not None:
|
if config.get("lookahead_analysis_exportfilename") is not None:
|
||||||
LookaheadAnalysisSubFunctions.export_to_csv(config, lookaheadAnalysis_instances)
|
LookaheadAnalysisSubFunctions.export_to_csv(config, lookaheadAnalysis_instances)
|
||||||
|
|
|
@ -168,8 +168,6 @@ max-complexity = 12
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"freqtrade/freqai/**/*.py" = [
|
"freqtrade/freqai/**/*.py" = [
|
||||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
|
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||||
"B006", # Bugbear - mutable default argument
|
|
||||||
"B008", # bugbear - Do not perform function calls in argument defaults
|
|
||||||
]
|
]
|
||||||
"tests/**/*.py" = [
|
"tests/**/*.py" = [
|
||||||
"S101", # allow assert in tests
|
"S101", # allow assert in tests
|
||||||
|
|
|
@ -151,7 +151,9 @@ def test_get_pair_data_for_features_with_prealoaded_data(mocker, freqai_conf):
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
_, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
|
_, base_df = freqai.dd.get_base_and_corr_dataframes(timerange, "LTC/BTC", freqai.dk)
|
||||||
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
|
df = freqai.dk.get_pair_data_for_features(
|
||||||
|
"LTC/BTC", "5m", strategy, {}, base_dataframes=base_df
|
||||||
|
)
|
||||||
|
|
||||||
assert df is base_df["5m"]
|
assert df is base_df["5m"]
|
||||||
assert not df.empty
|
assert not df.empty
|
||||||
|
@ -171,7 +173,9 @@ def test_get_pair_data_for_features_without_preloaded_data(mocker, freqai_conf):
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
base_df = {"5m": pd.DataFrame()}
|
base_df = {"5m": pd.DataFrame()}
|
||||||
df = freqai.dk.get_pair_data_for_features("LTC/BTC", "5m", strategy, base_dataframes=base_df)
|
df = freqai.dk.get_pair_data_for_features(
|
||||||
|
"LTC/BTC", "5m", strategy, {}, base_dataframes=base_df
|
||||||
|
)
|
||||||
|
|
||||||
assert df is not base_df["5m"]
|
assert df is not base_df["5m"]
|
||||||
assert not df.empty
|
assert not df.empty
|
||||||
|
|
|
@ -13,6 +13,12 @@ from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFu
|
||||||
from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
|
from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
|
||||||
|
|
||||||
|
|
||||||
|
IGNORE_BIASED_INDICATORS_CAPTION = (
|
||||||
|
"Any indicators in 'biased_indicators' which are used within "
|
||||||
|
"set_freqai_targets() can be ignored."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def lookahead_conf(default_conf_usdt, tmp_path):
|
def lookahead_conf(default_conf_usdt, tmp_path):
|
||||||
default_conf_usdt["user_data_dir"] = tmp_path
|
default_conf_usdt["user_data_dir"] = tmp_path
|
||||||
|
@ -133,6 +139,58 @@ def test_lookahead_helper_start(lookahead_conf, mocker) -> None:
|
||||||
text_table_mock.reset_mock()
|
text_table_mock.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"indicators, expected_caption_text",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
["&indicator1", "indicator2"],
|
||||||
|
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
["indicator1", "&indicator2"],
|
||||||
|
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
["&indicator1", "&indicator2"],
|
||||||
|
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||||
|
),
|
||||||
|
(["indicator1", "indicator2"], None),
|
||||||
|
([], None),
|
||||||
|
],
|
||||||
|
ids=(
|
||||||
|
"First of two biased indicators starts with '&'",
|
||||||
|
"Second of two biased indicators starts with '&'",
|
||||||
|
"Both biased indicators start with '&'",
|
||||||
|
"No biased indicators start with '&'",
|
||||||
|
"Empty biased indicators list",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_lookahead_helper_start__caption_based_on_indicators(
|
||||||
|
indicators, expected_caption_text, lookahead_conf, mocker
|
||||||
|
):
|
||||||
|
"""Test that the table caption is only populated if a biased_indicator starts with '&'."""
|
||||||
|
|
||||||
|
single_mock = MagicMock()
|
||||||
|
lookahead_analysis = LookaheadAnalysis(
|
||||||
|
lookahead_conf,
|
||||||
|
{"name": "strategy_test_v3_with_lookahead_bias"},
|
||||||
|
)
|
||||||
|
lookahead_analysis.current_analysis.false_indicators = indicators
|
||||||
|
single_mock.return_value = lookahead_analysis
|
||||||
|
text_table_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
"freqtrade.optimize.analysis.lookahead_helpers.LookaheadAnalysisSubFunctions",
|
||||||
|
initialize_single_lookahead_analysis=single_mock,
|
||||||
|
text_table_lookahead_analysis_instances=text_table_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
LookaheadAnalysisSubFunctions.start(lookahead_conf)
|
||||||
|
|
||||||
|
text_table_mock.assert_called_once_with(
|
||||||
|
lookahead_conf, [lookahead_analysis], caption=expected_caption_text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf):
|
def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf):
|
||||||
analysis = Analysis()
|
analysis = Analysis()
|
||||||
analysis.has_bias = True
|
analysis.has_bias = True
|
||||||
|
@ -199,6 +257,53 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||||
assert len(data) == 3
|
assert len(data) == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"caption",
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"A test caption",
|
||||||
|
None,
|
||||||
|
False,
|
||||||
|
],
|
||||||
|
ids=(
|
||||||
|
"Pass empty string",
|
||||||
|
"Pass non-empty string",
|
||||||
|
"Pass None",
|
||||||
|
"Don't pass caption",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_lookahead_helper_text_table_lookahead_analysis_instances__caption(
|
||||||
|
caption,
|
||||||
|
lookahead_conf,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
|
"""Test that the caption is passed in the table kwargs when calling print_rich_table()."""
|
||||||
|
|
||||||
|
print_rich_table_mock = MagicMock()
|
||||||
|
mocker.patch(
|
||||||
|
"freqtrade.optimize.analysis.lookahead_helpers.print_rich_table",
|
||||||
|
print_rich_table_mock,
|
||||||
|
)
|
||||||
|
lookahead_analysis = LookaheadAnalysis(
|
||||||
|
lookahead_conf,
|
||||||
|
{
|
||||||
|
"name": "strategy_test_v3_with_lookahead_bias",
|
||||||
|
"location": Path(lookahead_conf["strategy_path"], f"{lookahead_conf['strategy']}.py"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
kwargs = {}
|
||||||
|
if caption is not False:
|
||||||
|
kwargs["caption"] = caption
|
||||||
|
|
||||||
|
LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||||
|
lookahead_conf, [lookahead_analysis], **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
assert print_rich_table_mock.call_args[-1]["table_kwargs"]["caption"] == (
|
||||||
|
caption if caption is not False else None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_lookahead_helper_export_to_csv(lookahead_conf):
|
def test_lookahead_helper_export_to_csv(lookahead_conf):
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user