mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge remote-tracking branch 'upstream/develop' into feature/fetch-public-trades
This commit is contained in:
commit
05b2d8a2bf
|
@ -9,7 +9,7 @@ repos:
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: "v1.10.0"
|
rev: "v1.10.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
exclude: build_helpers
|
exclude: build_helpers
|
||||||
|
@ -31,7 +31,7 @@ repos:
|
||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: 'v0.4.10'
|
rev: 'v0.5.0'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.32-cp310-cp310-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.32-cp310-cp310-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.32-cp311-cp311-linux_armv7l.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.32-cp311-cp311-linux_armv7l.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.32-cp311-cp311-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.32-cp311-cp311-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.32-cp312-cp312-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.32-cp312-cp312-win_amd64.whl
Normal file
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.32-cp39-cp39-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.32-cp39-cp39-win_amd64.whl
Normal file
Binary file not shown.
|
@ -650,9 +650,9 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo
|
||||||
* API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in dry-run mode.
|
* API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in dry-run mode.
|
||||||
* Wallets (`/balance`) are simulated based on `dry_run_wallet`.
|
* Wallets (`/balance`) are simulated based on `dry_run_wallet`.
|
||||||
* Orders are simulated, and will not be posted to the exchange.
|
* Orders are simulated, and will not be posted to the exchange.
|
||||||
* Market orders fill based on orderbook volume the moment the order is placed.
|
* Market orders fill based on orderbook volume the moment the order is placed, with a maximum slippage of 5%.
|
||||||
* Limit orders fill once the price reaches the defined level - or time out based on `unfilledtimeout` settings.
|
* Limit orders fill once the price reaches the defined level - or time out based on `unfilledtimeout` settings.
|
||||||
* Limit orders will be converted to market orders if they cross the price by more than 1%.
|
* Limit orders will be converted to market orders if they cross the price by more than 1%, and will be filled immediately based regular market order rules (see point about Market orders above).
|
||||||
* In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled.
|
* In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled.
|
||||||
* Open orders (not trades, which are stored in the database) are kept open after bot restarts, with the assumption that they were not filled while being offline.
|
* Open orders (not trades, which are stored in the database) are kept open after bot restarts, with the assumption that they were not filled while being offline.
|
||||||
|
|
||||||
|
|
|
@ -73,11 +73,11 @@ Backtesting mode requires [downloading the necessary data](#downloading-data-to-
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Saving prediction data
|
### Saving backtesting prediction data
|
||||||
|
|
||||||
To allow for tweaking your strategy (**not** the features!), FreqAI will automatically save the predictions during backtesting so that they can be reused for future backtests and live runs using the same `identifier` model. This provides a performance enhancement geared towards enabling **high-level hyperopting** of entry/exit criteria.
|
To allow for tweaking your strategy (**not** the features!), FreqAI will automatically save the predictions during backtesting so that they can be reused for future backtests and live runs using the same `identifier` model. This provides a performance enhancement geared towards enabling **high-level hyperopting** of entry/exit criteria.
|
||||||
|
|
||||||
An additional directory called `backtesting_predictions`, which contains all the predictions stored in `hdf` format, will be created in the `unique-id` folder.
|
An additional directory called `backtesting_predictions`, which contains all the predictions stored in `feather` format, will be created in the `unique-id` folder.
|
||||||
|
|
||||||
To change your **features**, you **must** set a new `identifier` in the config to signal to FreqAI to train new models.
|
To change your **features**, you **must** set a new `identifier` in the config to signal to FreqAI to train new models.
|
||||||
|
|
||||||
|
@ -89,7 +89,6 @@ FreqAI allow you to reuse live historic predictions through the backtest paramet
|
||||||
|
|
||||||
The `--timerange` parameter must not be informed, as it will be automatically calculated through the data in the historic predictions file.
|
The `--timerange` parameter must not be informed, as it will be automatically calculated through the data in the historic predictions file.
|
||||||
|
|
||||||
|
|
||||||
### Downloading data to cover the full backtest period
|
### Downloading data to cover the full backtest period
|
||||||
|
|
||||||
For live/dry deployments, FreqAI will download the necessary data automatically. However, to use backtesting functionality, you need to download the necessary data using `download-data` (details [here](data-download.md#data-downloading)). You need to pay careful attention to understanding how much *additional* data needs to be downloaded to ensure that there is a sufficient amount of training data *before* the start of the backtesting time range. The amount of additional data can be roughly estimated by moving the start date of the time range backwards by `train_period_days` and the `startup_candle_count` (see the [parameter table](freqai-parameter-table.md) for detailed descriptions of these parameters) from the beginning of the desired backtesting time range.
|
For live/dry deployments, FreqAI will download the necessary data automatically. However, to use backtesting functionality, you need to download the necessary data using `download-data` (details [here](data-download.md#data-downloading)). You need to pay careful attention to understanding how much *additional* data needs to be downloaded to ensure that there is a sufficient amount of training data *before* the start of the backtesting time range. The amount of additional data can be roughly estimated by moving the start date of the time range backwards by `train_period_days` and the `startup_candle_count` (see the [parameter table](freqai-parameter-table.md) for detailed descriptions of these parameters) from the beginning of the desired backtesting time range.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Freqtrade bot"""
|
"""Freqtrade bot"""
|
||||||
|
|
||||||
__version__ = "2024.6-dev"
|
__version__ = "2024.7-dev"
|
||||||
|
|
||||||
if "dev" in __version__:
|
if "dev" in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
This module contains the argument manager class
|
This module contains the argument manager class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
from argparse import ArgumentParser, Namespace, _ArgumentGroup
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
||||||
from freqtrade.constants import DEFAULT_CONFIG
|
from freqtrade.constants import DEFAULT_CONFIG
|
||||||
|
@ -226,6 +226,19 @@ ARGS_ANALYZE_ENTRIES_EXITS = [
|
||||||
"analysis_csv_path",
|
"analysis_csv_path",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_search"]
|
||||||
|
|
||||||
|
ARGS_LOOKAHEAD_ANALYSIS = [
|
||||||
|
a
|
||||||
|
for a in ARGS_BACKTEST
|
||||||
|
if a
|
||||||
|
not in ("position_stacking", "use_max_market_positions", "backtest_cache", "backtest_breakdown")
|
||||||
|
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
|
||||||
|
|
||||||
|
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
|
||||||
|
|
||||||
|
# Command level configs - keep at the bottom of the above definitions
|
||||||
NO_CONF_REQURIED = [
|
NO_CONF_REQURIED = [
|
||||||
"convert-data",
|
"convert-data",
|
||||||
"convert-trade-data",
|
"convert-trade-data",
|
||||||
|
@ -248,14 +261,6 @@ NO_CONF_REQURIED = [
|
||||||
|
|
||||||
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"]
|
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"]
|
||||||
|
|
||||||
ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_search"]
|
|
||||||
|
|
||||||
ARGS_LOOKAHEAD_ANALYSIS = [
|
|
||||||
a for a in ARGS_BACKTEST if a not in ("position_stacking", "use_max_market_positions", "cache")
|
|
||||||
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
|
|
||||||
|
|
||||||
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
|
|
||||||
|
|
||||||
|
|
||||||
class Arguments:
|
class Arguments:
|
||||||
"""
|
"""
|
||||||
|
@ -264,7 +269,7 @@ class Arguments:
|
||||||
|
|
||||||
def __init__(self, args: Optional[List[str]]) -> None:
|
def __init__(self, args: Optional[List[str]]) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self._parsed_arg: Optional[argparse.Namespace] = None
|
self._parsed_arg: Optional[Namespace] = None
|
||||||
|
|
||||||
def get_parsed_arg(self) -> Dict[str, Any]:
|
def get_parsed_arg(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -277,7 +282,7 @@ class Arguments:
|
||||||
|
|
||||||
return vars(self._parsed_arg)
|
return vars(self._parsed_arg)
|
||||||
|
|
||||||
def _parse_args(self) -> argparse.Namespace:
|
def _parse_args(self) -> Namespace:
|
||||||
"""
|
"""
|
||||||
Parses given arguments and returns an argparse Namespace instance.
|
Parses given arguments and returns an argparse Namespace instance.
|
||||||
"""
|
"""
|
||||||
|
@ -306,7 +311,9 @@ class Arguments:
|
||||||
|
|
||||||
return parsed_arg
|
return parsed_arg
|
||||||
|
|
||||||
def _build_args(self, optionlist, parser):
|
def _build_args(
|
||||||
|
self, optionlist: List[str], parser: Union[ArgumentParser, _ArgumentGroup]
|
||||||
|
) -> None:
|
||||||
for val in optionlist:
|
for val in optionlist:
|
||||||
opt = AVAILABLE_CLI_OPTIONS[val]
|
opt = AVAILABLE_CLI_OPTIONS[val]
|
||||||
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
|
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
|
||||||
|
@ -317,16 +324,16 @@ class Arguments:
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
# Build shared arguments (as group Common Options)
|
# Build shared arguments (as group Common Options)
|
||||||
_common_parser = argparse.ArgumentParser(add_help=False)
|
_common_parser = ArgumentParser(add_help=False)
|
||||||
group = _common_parser.add_argument_group("Common arguments")
|
group = _common_parser.add_argument_group("Common arguments")
|
||||||
self._build_args(optionlist=ARGS_COMMON, parser=group)
|
self._build_args(optionlist=ARGS_COMMON, parser=group)
|
||||||
|
|
||||||
_strategy_parser = argparse.ArgumentParser(add_help=False)
|
_strategy_parser = ArgumentParser(add_help=False)
|
||||||
strategy_group = _strategy_parser.add_argument_group("Strategy arguments")
|
strategy_group = _strategy_parser.add_argument_group("Strategy arguments")
|
||||||
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
|
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
|
||||||
|
|
||||||
# Build main command
|
# Build main command
|
||||||
self.parser = argparse.ArgumentParser(
|
self.parser = ArgumentParser(
|
||||||
prog="freqtrade", description="Free, open source crypto trading bot"
|
prog="freqtrade", description="Free, open source crypto trading bot"
|
||||||
)
|
)
|
||||||
self._build_args(optionlist=["version"], parser=self.parser)
|
self._build_args(optionlist=["version"], parser=self.parser)
|
||||||
|
|
|
@ -45,7 +45,8 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
||||||
"name": exchange["name"],
|
"name": exchange["name"],
|
||||||
**valid_entry,
|
**valid_entry,
|
||||||
"supported": "Official" if exchange["supported"] else "",
|
"supported": "Official" if exchange["supported"] else "",
|
||||||
"trade_modes": ", ".join(
|
"trade_modes": ("DEX: " if exchange["dex"] else "")
|
||||||
|
+ ", ".join(
|
||||||
(f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"]
|
(f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"]
|
||||||
for a in exchange["trade_modes"]
|
for a in exchange["trade_modes"]
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,5 +2,8 @@
|
||||||
Module to handle data operations for freqtrade
|
Module to handle data operations for freqtrade
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from freqtrade.data import converter
|
||||||
|
|
||||||
|
|
||||||
# limit what's imported when using `from freqtrade.data import *`
|
# limit what's imported when using `from freqtrade.data import *`
|
||||||
__all__ = ["converter"]
|
__all__ = ["converter"]
|
||||||
|
|
|
@ -8437,7 +8437,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"BTC/USDT:USDT-240628": [
|
"BTC/USDT:USDT-240927": [
|
||||||
{
|
{
|
||||||
"tier": 1.0,
|
"tier": 1.0,
|
||||||
"currency": "USDT",
|
"currency": "USDT",
|
||||||
|
@ -8567,7 +8567,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"BTC/USDT:USDT-240927": [
|
"BTC/USDT:USDT-241227": [
|
||||||
{
|
{
|
||||||
"tier": 1.0,
|
"tier": 1.0,
|
||||||
"currency": "USDT",
|
"currency": "USDT",
|
||||||
|
@ -13805,7 +13805,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ETH/USDT:USDT-240628": [
|
"ETH/USDT:USDT-240927": [
|
||||||
{
|
{
|
||||||
"tier": 1.0,
|
"tier": 1.0,
|
||||||
"currency": "USDT",
|
"currency": "USDT",
|
||||||
|
@ -13935,7 +13935,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ETH/USDT:USDT-240927": [
|
"ETH/USDT:USDT-241227": [
|
||||||
{
|
{
|
||||||
"tier": 1.0,
|
"tier": 1.0,
|
||||||
"currency": "USDT",
|
"currency": "USDT",
|
||||||
|
|
|
@ -47,7 +47,7 @@ def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
|
||||||
f'{", ".join(available_exchanges())}'
|
f'{", ".join(available_exchanges())}'
|
||||||
)
|
)
|
||||||
|
|
||||||
valid, reason = validate_exchange(exchange)
|
valid, reason, _ = validate_exchange(exchange)
|
||||||
if not valid:
|
if not valid:
|
||||||
if check_for_bad:
|
if check_for_bad:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
|
|
@ -53,16 +53,19 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st
|
||||||
return [x for x in exchanges if validate_exchange(x)[0]]
|
return [x for x in exchanges if validate_exchange(x)[0]]
|
||||||
|
|
||||||
|
|
||||||
def validate_exchange(exchange: str) -> Tuple[bool, str]:
|
def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
|
||||||
"""
|
"""
|
||||||
returns: can_use, reason
|
returns: can_use, reason
|
||||||
with Reason including both missing and missing_opt
|
with Reason including both missing and missing_opt
|
||||||
"""
|
"""
|
||||||
ex_mod = getattr(ccxt, exchange.lower())()
|
ex_mod = getattr(ccxt, exchange.lower())()
|
||||||
|
|
||||||
|
if not ex_mod or not ex_mod.has:
|
||||||
|
return False, "", False
|
||||||
|
|
||||||
result = True
|
result = True
|
||||||
reason = ""
|
reason = ""
|
||||||
if not ex_mod or not ex_mod.has:
|
is_dex = getattr(ex_mod, "dex", False)
|
||||||
return False, ""
|
|
||||||
missing = [
|
missing = [
|
||||||
k
|
k
|
||||||
for k, v in EXCHANGE_HAS_REQUIRED.items()
|
for k, v in EXCHANGE_HAS_REQUIRED.items()
|
||||||
|
@ -81,18 +84,19 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]:
|
||||||
if missing_opt:
|
if missing_opt:
|
||||||
reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. "
|
reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. "
|
||||||
|
|
||||||
return result, reason
|
return result, reason, is_dex
|
||||||
|
|
||||||
|
|
||||||
def _build_exchange_list_entry(
|
def _build_exchange_list_entry(
|
||||||
exchange_name: str, exchangeClasses: Dict[str, Any]
|
exchange_name: str, exchangeClasses: Dict[str, Any]
|
||||||
) -> ValidExchangesType:
|
) -> ValidExchangesType:
|
||||||
valid, comment = validate_exchange(exchange_name)
|
valid, comment, is_dex = validate_exchange(exchange_name)
|
||||||
result: ValidExchangesType = {
|
result: ValidExchangesType = {
|
||||||
"name": exchange_name,
|
"name": exchange_name,
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
"supported": exchange_name.lower() in SUPPORTED_EXCHANGES,
|
"supported": exchange_name.lower() in SUPPORTED_EXCHANGES,
|
||||||
"comment": comment,
|
"comment": comment,
|
||||||
|
"dex": is_dex,
|
||||||
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
|
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
|
||||||
}
|
}
|
||||||
if resolved := exchangeClasses.get(exchange_name.lower()):
|
if resolved := exchangeClasses.get(exchange_name.lower()):
|
||||||
|
|
|
@ -33,7 +33,7 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool =
|
||||||
if log:
|
if log:
|
||||||
logger.info(f'dumping json to "{filename}"')
|
logger.info(f'dumping json to "{filename}"')
|
||||||
|
|
||||||
with gzip.open(filename, "w") as fpz:
|
with gzip.open(filename, "wt", encoding="utf-8") as fpz:
|
||||||
rapidjson.dump(data, fpz, default=str, number_mode=rapidjson.NM_NATIVE)
|
rapidjson.dump(data, fpz, default=str, number_mode=rapidjson.NM_NATIVE)
|
||||||
else:
|
else:
|
||||||
if log:
|
if log:
|
||||||
|
@ -60,7 +60,7 @@ def file_dump_joblib(filename: Path, data: Any, log: bool = True) -> None:
|
||||||
logger.debug(f'done joblib dump to "{filename}"')
|
logger.debug(f'done joblib dump to "{filename}"')
|
||||||
|
|
||||||
|
|
||||||
def json_load(datafile: Union[gzip.GzipFile, TextIO]) -> Any:
|
def json_load(datafile: TextIO) -> Any:
|
||||||
"""
|
"""
|
||||||
load data with rapidjson
|
load data with rapidjson
|
||||||
Use this to have a consistent experience,
|
Use this to have a consistent experience,
|
||||||
|
@ -77,7 +77,7 @@ def file_load_json(file: Path):
|
||||||
# Try gzip file first, otherwise regular json file.
|
# Try gzip file first, otherwise regular json file.
|
||||||
if gzipfile.is_file():
|
if gzipfile.is_file():
|
||||||
logger.debug(f"Loading historical data from file {gzipfile}")
|
logger.debug(f"Loading historical data from file {gzipfile}")
|
||||||
with gzip.open(gzipfile) as datafile:
|
with gzip.open(gzipfile, "rt", encoding="utf-8") as datafile:
|
||||||
pairdata = json_load(datafile)
|
pairdata = json_load(datafile)
|
||||||
elif file.is_file():
|
elif file.is_file():
|
||||||
logger.debug(f"Loading historical data from file {file}")
|
logger.debug(f"Loading historical data from file {file}")
|
||||||
|
|
|
@ -217,8 +217,6 @@ class Backtesting:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"VolumePairList not allowed for backtesting. Please use StaticPairList instead."
|
"VolumePairList not allowed for backtesting. Please use StaticPairList instead."
|
||||||
)
|
)
|
||||||
if "PerformanceFilter" in self.pairlists.name_list:
|
|
||||||
raise OperationalException("PerformanceFilter not allowed for backtesting.")
|
|
||||||
|
|
||||||
if len(self.strategylist) > 1 and "PrecisionFilter" in self.pairlists.name_list:
|
if len(self.strategylist) > 1 and "PrecisionFilter" in self.pairlists.name_list:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
|
|
@ -13,7 +13,7 @@ from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import plural
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util import PeriodicCache, dt_floor_day, dt_now, dt_ts
|
from freqtrade.util import PeriodicCache, dt_floor_day, dt_now, dt_ts
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AgeFilter(IPairList):
|
class AgeFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,15 @@ from typing import List
|
||||||
|
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FullTradesFilter(IPairList):
|
class FullTradesFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.NO_ACTION
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def needstickers(self) -> bool:
|
def needstickers(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,6 +5,7 @@ PairList Handler base class
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
|
@ -51,8 +52,20 @@ PairlistParameter = Union[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsBacktesting(str, Enum):
|
||||||
|
"""
|
||||||
|
Enum to indicate if a Pairlist Handler supports backtesting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
YES = "yes"
|
||||||
|
NO = "no"
|
||||||
|
NO_ACTION = "no_action"
|
||||||
|
BIASED = "biased"
|
||||||
|
|
||||||
|
|
||||||
class IPairList(LoggingMixin, ABC):
|
class IPairList(LoggingMixin, ABC):
|
||||||
is_pairlist_generator = False
|
is_pairlist_generator = False
|
||||||
|
supports_backtesting: SupportsBacktesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -11,7 +11,7 @@ from cachetools import TTLCache
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util.coin_gecko import FtCoinGeckoApi
|
from freqtrade.util.coin_gecko import FtCoinGeckoApi
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MarketCapPairList(IPairList):
|
class MarketCapPairList(IPairList):
|
||||||
is_pairlist_generator = True
|
is_pairlist_generator = True
|
||||||
|
supports_backtesting = SupportsBacktesting.BIASED
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -7,13 +7,15 @@ from typing import Dict, List
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OffsetFilter(IPairList):
|
class OffsetFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.YES
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,15 @@ import pandas as pd
|
||||||
|
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PerformanceFilter(IPairList):
|
class PerformanceFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.NO_ACTION
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,15 @@ from typing import Optional
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import ROUND_UP
|
from freqtrade.exchange import ROUND_UP
|
||||||
from freqtrade.exchange.types import Ticker
|
from freqtrade.exchange.types import Ticker
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PrecisionFilter(IPairList):
|
class PrecisionFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.BIASED
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,15 @@ from typing import Dict, Optional
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Ticker
|
from freqtrade.exchange.types import Ticker
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PriceFilter(IPairList):
|
class PriceFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.BIASED
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from typing import Dict, List, Optional
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -31,6 +31,7 @@ class ProducerPairList(IPairList):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
is_pairlist_generator = True
|
is_pairlist_generator = True
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -16,7 +16,7 @@ from freqtrade import __version__
|
||||||
from freqtrade.configuration.load_config import CONFIG_PARSE_MODE
|
from freqtrade.configuration.load_config import CONFIG_PARSE_MODE
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class RemotePairList(IPairList):
|
class RemotePairList(IPairList):
|
||||||
is_pairlist_generator = True
|
is_pairlist_generator = True
|
||||||
|
# Potential winner bias
|
||||||
|
supports_backtesting = SupportsBacktesting.BIASED
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from typing import Dict, List, Literal
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exchange import timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_seconds
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util.periodic_cache import PeriodicCache
|
from freqtrade.util.periodic_cache import PeriodicCache
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ ShuffleValues = Literal["candle", "iteration"]
|
||||||
|
|
||||||
|
|
||||||
class ShuffleFilter(IPairList):
|
class ShuffleFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.YES
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,15 @@ from typing import Dict, Optional
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Ticker
|
from freqtrade.exchange.types import Ticker
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SpreadFilter(IPairList):
|
class SpreadFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from copy import deepcopy
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class StaticPairList(IPairList):
|
class StaticPairList(IPairList):
|
||||||
is_pairlist_generator = True
|
is_pairlist_generator = True
|
||||||
|
supports_backtesting = SupportsBacktesting.YES
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -15,7 +15,7 @@ from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import plural
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util import dt_floor_day, dt_now, dt_ts
|
from freqtrade.util import dt_floor_day, dt_now, dt_ts
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@ class VolatilityFilter(IPairList):
|
||||||
Filters pairs by volatility
|
Filters pairs by volatility
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util import dt_now, format_ms_time
|
from freqtrade.util import dt_now, format_ms_time
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ SORT_VALUES = ["quoteVolume"]
|
||||||
|
|
||||||
class VolumePairList(IPairList):
|
class VolumePairList(IPairList):
|
||||||
is_pairlist_generator = True
|
is_pairlist_generator = True
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -13,7 +13,7 @@ from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import plural
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
|
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||||
from freqtrade.util import dt_floor_day, dt_now, dt_ts
|
from freqtrade.util import dt_floor_day, dt_now, dt_ts
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RangeStabilityFilter(IPairList):
|
class RangeStabilityFilter(IPairList):
|
||||||
|
supports_backtesting = SupportsBacktesting.NO
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,11 @@ from cachetools import TTLCache, cached
|
||||||
from freqtrade.constants import Config, ListPairsWithTimeframes
|
from freqtrade.constants import Config, ListPairsWithTimeframes
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType
|
||||||
|
from freqtrade.enums.runmode import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange.types import Tickers
|
from freqtrade.exchange.types import Tickers
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.resolvers import PairListResolver
|
from freqtrade.resolvers import PairListResolver
|
||||||
|
|
||||||
|
@ -57,9 +58,44 @@ class PairListManager(LoggingMixin):
|
||||||
f"{invalid}."
|
f"{invalid}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._check_backtest()
|
||||||
|
|
||||||
refresh_period = config.get("pairlist_refresh_period", 3600)
|
refresh_period = config.get("pairlist_refresh_period", 3600)
|
||||||
LoggingMixin.__init__(self, logger, refresh_period)
|
LoggingMixin.__init__(self, logger, refresh_period)
|
||||||
|
|
||||||
|
def _check_backtest(self) -> None:
|
||||||
|
if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT):
|
||||||
|
return
|
||||||
|
|
||||||
|
pairlist_errors: List[str] = []
|
||||||
|
noaction_pairlists: List[str] = []
|
||||||
|
biased_pairlists: List[str] = []
|
||||||
|
for pairlist_handler in self._pairlist_handlers:
|
||||||
|
if pairlist_handler.supports_backtesting == SupportsBacktesting.NO:
|
||||||
|
pairlist_errors.append(pairlist_handler.name)
|
||||||
|
if pairlist_handler.supports_backtesting == SupportsBacktesting.NO_ACTION:
|
||||||
|
noaction_pairlists.append(pairlist_handler.name)
|
||||||
|
if pairlist_handler.supports_backtesting == SupportsBacktesting.BIASED:
|
||||||
|
biased_pairlists.append(pairlist_handler.name)
|
||||||
|
|
||||||
|
if noaction_pairlists:
|
||||||
|
logger.warning(
|
||||||
|
f"Pairlist Handlers {', '.join(noaction_pairlists)} do not generate "
|
||||||
|
"any changes during backtesting. While it's safe to leave them enabled, they will "
|
||||||
|
"not behave like in dry/live modes. "
|
||||||
|
)
|
||||||
|
|
||||||
|
if biased_pairlists:
|
||||||
|
logger.warning(
|
||||||
|
f"Pairlist Handlers {', '.join(biased_pairlists)} will introduce a lookahead bias "
|
||||||
|
"to your backtest results, as they use today's data - which inheritly suffers from "
|
||||||
|
"'winner bias'."
|
||||||
|
)
|
||||||
|
if pairlist_errors:
|
||||||
|
raise OperationalException(
|
||||||
|
f"Pairlist Handlers {', '.join(pairlist_errors)} do not support backtesting."
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def whitelist(self) -> List[str]:
|
def whitelist(self) -> List[str]:
|
||||||
"""The current whitelist"""
|
"""The current whitelist"""
|
||||||
|
|
|
@ -1787,7 +1787,7 @@ class Telegram(RPCHandler):
|
||||||
"_Bot Control_\n"
|
"_Bot Control_\n"
|
||||||
"------------\n"
|
"------------\n"
|
||||||
"*/start:* `Starts the trader`\n"
|
"*/start:* `Starts the trader`\n"
|
||||||
"*/stop:* Stops the trader\n"
|
"*/stop:* `Stops the trader`\n"
|
||||||
"*/stopentry:* `Stops entering, but handles open trades gracefully` \n"
|
"*/stopentry:* `Stops entering, but handles open trades gracefully` \n"
|
||||||
"*/forceexit <trade_id>|all:* `Instantly exits the given trade or all trades, "
|
"*/forceexit <trade_id>|all:* `Instantly exits the given trade or all trades, "
|
||||||
"regardless of profit`\n"
|
"regardless of profit`\n"
|
||||||
|
@ -1820,7 +1820,7 @@ class Telegram(RPCHandler):
|
||||||
"that represents the current market direction. If no direction is provided `"
|
"that represents the current market direction. If no direction is provided `"
|
||||||
"`the currently set market direction will be output.` \n"
|
"`the currently set market direction will be output.` \n"
|
||||||
"*/list_custom_data <trade_id> <key>:* `List custom_data for Trade ID & Key combo.`\n"
|
"*/list_custom_data <trade_id> <key>:* `List custom_data for Trade ID & Key combo.`\n"
|
||||||
"`If no Key is supplied it will list all key-value pairs found for that Trade ID.`"
|
"`If no Key is supplied it will list all key-value pairs found for that Trade ID.`\n"
|
||||||
"_Statistics_\n"
|
"_Statistics_\n"
|
||||||
"------------\n"
|
"------------\n"
|
||||||
"*/status <trade_id>|[table]:* `Lists all open trades`\n"
|
"*/status <trade_id>|[table]:* `Lists all open trades`\n"
|
||||||
|
|
|
@ -14,4 +14,5 @@ class ValidExchangesType(TypedDict):
|
||||||
valid: bool
|
valid: bool
|
||||||
supported: bool
|
supported: bool
|
||||||
comment: str
|
comment: str
|
||||||
|
dex: bool
|
||||||
trade_modes: List[TradeModeType]
|
trade_modes: List[TradeModeType]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from freqtrade_client.ft_rest_client import FtRestClient
|
from freqtrade_client.ft_rest_client import FtRestClient
|
||||||
|
|
||||||
|
|
||||||
__version__ = "2024.6-dev"
|
__version__ = "2024.7-dev"
|
||||||
|
|
||||||
if "dev" in __version__:
|
if "dev" in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Requirements for freqtrade client library
|
# Requirements for freqtrade client library
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
python-rapidjson==1.17
|
python-rapidjson==1.18
|
||||||
|
|
|
@ -144,6 +144,7 @@ extend-select = [
|
||||||
# "TCH", # flake8-type-checking
|
# "TCH", # flake8-type-checking
|
||||||
"PTH", # flake8-use-pathlib
|
"PTH", # flake8-use-pathlib
|
||||||
# "RUF", # ruff
|
# "RUF", # ruff
|
||||||
|
"ASYNC", # flake8-async
|
||||||
]
|
]
|
||||||
|
|
||||||
extend-ignore = [
|
extend-ignore = [
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
-r docs/requirements-docs.txt
|
-r docs/requirements-docs.txt
|
||||||
|
|
||||||
coveralls==4.0.1
|
coveralls==4.0.1
|
||||||
ruff==0.4.10
|
ruff==0.5.0
|
||||||
mypy==1.10.0
|
mypy==1.10.1
|
||||||
pre-commit==3.7.1
|
pre-commit==3.7.1
|
||||||
pytest==8.2.2
|
pytest==8.2.2
|
||||||
pytest-asyncio==0.23.7
|
pytest-asyncio==0.23.7
|
||||||
|
@ -18,7 +18,7 @@ pytest-random-order==1.1.1
|
||||||
pytest-xdist==3.6.1
|
pytest-xdist==3.6.1
|
||||||
isort==5.13.2
|
isort==5.13.2
|
||||||
# For datetime mocking
|
# For datetime mocking
|
||||||
time-machine==2.14.1
|
time-machine==2.14.2
|
||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==7.16.4
|
nbconvert==7.16.4
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scipy==1.13.1
|
scipy==1.14.0; python_version >= "3.10"
|
||||||
|
scipy==1.13.1; python_version < "3.10"
|
||||||
scikit-learn==1.5.0
|
scikit-learn==1.5.0
|
||||||
ft-scikit-optimize==0.9.2
|
ft-scikit-optimize==0.9.2
|
||||||
filelock==3.15.4
|
filelock==3.15.4
|
||||||
|
|
|
@ -4,7 +4,7 @@ bottleneck==1.4.0
|
||||||
numexpr==2.10.1
|
numexpr==2.10.1
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.3.50
|
ccxt==4.3.54
|
||||||
cryptography==42.0.8
|
cryptography==42.0.8
|
||||||
aiohttp==3.9.5
|
aiohttp==3.9.5
|
||||||
SQLAlchemy==2.0.31
|
SQLAlchemy==2.0.31
|
||||||
|
@ -16,7 +16,7 @@ cachetools==5.3.3
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
urllib3==2.2.2
|
urllib3==2.2.2
|
||||||
jsonschema==4.22.0
|
jsonschema==4.22.0
|
||||||
TA-Lib==0.4.31
|
TA-Lib==0.4.32
|
||||||
technical==1.4.3
|
technical==1.4.3
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
pycoingecko==3.1.0
|
pycoingecko==3.1.0
|
||||||
|
@ -30,7 +30,7 @@ pyarrow==16.1.0; platform_machine != 'armv7l'
|
||||||
py_find_1st==1.1.6
|
py_find_1st==1.1.6
|
||||||
|
|
||||||
# Load ticker files 30% faster
|
# Load ticker files 30% faster
|
||||||
python-rapidjson==1.17
|
python-rapidjson==1.18
|
||||||
# Properly format api responses
|
# Properly format api responses
|
||||||
orjson==3.10.5
|
orjson==3.10.5
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ fastapi==0.111.0
|
||||||
pydantic==2.7.4
|
pydantic==2.7.4
|
||||||
uvicorn==0.30.1
|
uvicorn==0.30.1
|
||||||
pyjwt==2.8.0
|
pyjwt==2.8.0
|
||||||
aiofiles==23.2.1
|
aiofiles==24.1.0
|
||||||
psutil==6.0.0
|
psutil==6.0.0
|
||||||
|
|
||||||
# Support for colorized terminal output
|
# Support for colorized terminal output
|
||||||
|
|
|
@ -429,7 +429,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
|
||||||
backtesting.start()
|
backtesting.start()
|
||||||
|
|
||||||
|
|
||||||
def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_backtesting_no_pair_left(default_conf, mocker) -> None:
|
||||||
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
|
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"freqtrade.data.history.history_utils.load_pair_history",
|
"freqtrade.data.history.history_utils.load_pair_history",
|
||||||
|
@ -449,13 +449,6 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
|
||||||
with pytest.raises(OperationalException, match="No pair in whitelist."):
|
with pytest.raises(OperationalException, match="No pair in whitelist."):
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
default_conf["pairlists"] = [{"method": "VolumePairList", "number_assets": 5}]
|
|
||||||
with pytest.raises(
|
|
||||||
OperationalException,
|
|
||||||
match=r"VolumePairList not allowed for backtesting\..*StaticPairList.*",
|
|
||||||
):
|
|
||||||
Backtesting(default_conf)
|
|
||||||
|
|
||||||
default_conf.update(
|
default_conf.update(
|
||||||
{
|
{
|
||||||
"pairlists": [{"method": "StaticPairList"}],
|
"pairlists": [{"method": "StaticPairList"}],
|
||||||
|
@ -469,7 +462,7 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
|
def test_backtesting_pairlist_list(default_conf, mocker, tickers) -> None:
|
||||||
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
|
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
|
||||||
mocker.patch(f"{EXMS}.get_tickers", tickers)
|
mocker.patch(f"{EXMS}.get_tickers", tickers)
|
||||||
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
|
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
|
||||||
|
@ -495,12 +488,6 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
||||||
):
|
):
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
default_conf["pairlists"] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
|
|
||||||
with pytest.raises(
|
|
||||||
OperationalException, match="PerformanceFilter not allowed for backtesting."
|
|
||||||
):
|
|
||||||
Backtesting(default_conf)
|
|
||||||
|
|
||||||
default_conf["pairlists"] = [
|
default_conf["pairlists"] = [
|
||||||
{"method": "StaticPairList"},
|
{"method": "StaticPairList"},
|
||||||
{"method": "PrecisionFilter"},
|
{"method": "PrecisionFilter"},
|
||||||
|
|
|
@ -38,6 +38,7 @@ TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairLis
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def whitelist_conf(default_conf):
|
def whitelist_conf(default_conf):
|
||||||
|
default_conf["runmode"] = "dry_run"
|
||||||
default_conf["stake_currency"] = "BTC"
|
default_conf["stake_currency"] = "BTC"
|
||||||
default_conf["exchange"]["pair_whitelist"] = [
|
default_conf["exchange"]["pair_whitelist"] = [
|
||||||
"ETH/BTC",
|
"ETH/BTC",
|
||||||
|
@ -68,6 +69,7 @@ def whitelist_conf(default_conf):
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def whitelist_conf_2(default_conf):
|
def whitelist_conf_2(default_conf):
|
||||||
|
default_conf["runmode"] = "dry_run"
|
||||||
default_conf["stake_currency"] = "BTC"
|
default_conf["stake_currency"] = "BTC"
|
||||||
default_conf["exchange"]["pair_whitelist"] = [
|
default_conf["exchange"]["pair_whitelist"] = [
|
||||||
"ETH/BTC",
|
"ETH/BTC",
|
||||||
|
@ -94,6 +96,7 @@ def whitelist_conf_2(default_conf):
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def whitelist_conf_agefilter(default_conf):
|
def whitelist_conf_agefilter(default_conf):
|
||||||
|
default_conf["runmode"] = "dry_run"
|
||||||
default_conf["stake_currency"] = "BTC"
|
default_conf["stake_currency"] = "BTC"
|
||||||
default_conf["exchange"]["pair_whitelist"] = [
|
default_conf["exchange"]["pair_whitelist"] = [
|
||||||
"ETH/BTC",
|
"ETH/BTC",
|
||||||
|
@ -773,7 +776,7 @@ def test_VolumePairList_whitelist_gen(
|
||||||
whitelist_result,
|
whitelist_result,
|
||||||
caplog,
|
caplog,
|
||||||
) -> None:
|
) -> None:
|
||||||
whitelist_conf["runmode"] = "backtest"
|
whitelist_conf["runmode"] = "util_exchange"
|
||||||
whitelist_conf["pairlists"] = pairlists
|
whitelist_conf["pairlists"] = pairlists
|
||||||
whitelist_conf["stake_currency"] = base_currency
|
whitelist_conf["stake_currency"] = base_currency
|
||||||
|
|
||||||
|
@ -2387,3 +2390,65 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
|
||||||
OperationalException, match="This filter only support marketcap rank up to 250."
|
OperationalException, match="This filter only support marketcap rank up to 250."
|
||||||
):
|
):
|
||||||
PairListManager(exchange, default_conf_usdt)
|
PairListManager(exchange, default_conf_usdt)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pairlists,expected_error,expected_warning",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[{"method": "StaticPairList"}],
|
||||||
|
None, # Error
|
||||||
|
None, # Warning
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[{"method": "VolumePairList", "number_assets": 10}],
|
||||||
|
"VolumePairList", # Error
|
||||||
|
None, # Warning
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[{"method": "MarketCapPairList", "number_assets": 10}],
|
||||||
|
None, # Error
|
||||||
|
r"MarketCapPairList.*lookahead.*", # Warning
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[{"method": "StaticPairList"}, {"method": "FullTradesFilter"}],
|
||||||
|
None, # Error
|
||||||
|
r"FullTradesFilter do not generate.*", # Warning
|
||||||
|
),
|
||||||
|
( # combi, fails and warns
|
||||||
|
[
|
||||||
|
{"method": "VolumePairList", "number_assets": 10},
|
||||||
|
{"method": "MarketCapPairList", "number_assets": 10},
|
||||||
|
],
|
||||||
|
"VolumePairList", # Error
|
||||||
|
r"MarketCapPairList.*lookahead.*", # Warning
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_backtesting_modes(
|
||||||
|
mocker, default_conf_usdt, pairlists, expected_error, expected_warning, caplog, markets, tickers
|
||||||
|
):
|
||||||
|
default_conf_usdt["runmode"] = "dry_run"
|
||||||
|
default_conf_usdt["pairlists"] = pairlists
|
||||||
|
|
||||||
|
mocker.patch.multiple(
|
||||||
|
EXMS,
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
exchange_has=MagicMock(return_value=True),
|
||||||
|
get_tickers=tickers,
|
||||||
|
)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf_usdt)
|
||||||
|
|
||||||
|
# Dry run mode - works always
|
||||||
|
PairListManager(exchange, default_conf_usdt)
|
||||||
|
|
||||||
|
default_conf_usdt["runmode"] = "backtest"
|
||||||
|
if expected_error:
|
||||||
|
with pytest.raises(OperationalException, match=f"Pairlist Handlers {expected_error}.*"):
|
||||||
|
PairListManager(exchange, default_conf_usdt)
|
||||||
|
|
||||||
|
if not expected_error:
|
||||||
|
PairListManager(exchange, default_conf_usdt)
|
||||||
|
|
||||||
|
if expected_warning:
|
||||||
|
assert log_has_re(f"Pairlist Handlers {expected_warning}", caplog)
|
||||||
|
|
|
@ -2154,6 +2154,7 @@ def test_api_exchanges(botclient):
|
||||||
"valid": True,
|
"valid": True,
|
||||||
"supported": True,
|
"supported": True,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
|
"dex": False,
|
||||||
"trade_modes": [
|
"trade_modes": [
|
||||||
{"trading_mode": "spot", "margin_mode": ""},
|
{"trading_mode": "spot", "margin_mode": ""},
|
||||||
{"trading_mode": "futures", "margin_mode": "isolated"},
|
{"trading_mode": "futures", "margin_mode": "isolated"},
|
||||||
|
@ -2165,6 +2166,16 @@ def test_api_exchanges(botclient):
|
||||||
"name": "mexc",
|
"name": "mexc",
|
||||||
"valid": True,
|
"valid": True,
|
||||||
"supported": False,
|
"supported": False,
|
||||||
|
"dex": False,
|
||||||
|
"comment": "",
|
||||||
|
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
|
||||||
|
}
|
||||||
|
waves = [x for x in response["exchanges"] if x["name"] == "wavesexchange"][0]
|
||||||
|
assert waves == {
|
||||||
|
"name": "wavesexchange",
|
||||||
|
"valid": True,
|
||||||
|
"supported": False,
|
||||||
|
"dex": True,
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
|
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user