Merge branch 'develop' into dev-merge-rl

This commit is contained in:
robcaulk 2022-09-22 19:46:50 +02:00
commit ea8e34e192
121 changed files with 1525 additions and 564 deletions

View File

@ -24,7 +24,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -121,7 +121,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ macos-latest ] os: [ macos-latest ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -205,7 +205,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ windows-latest ] os: [ windows-latest ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -15,7 +15,7 @@ repos:
additional_dependencies: additional_dependencies:
- types-cachetools==5.2.1 - types-cachetools==5.2.1
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.9 - types-requests==2.28.10
- types-tabulate==0.8.11 - types-tabulate==0.8.11
- types-python-dateutil==2.8.19 - types-python-dateutil==2.8.19
# stages: [push] # stages: [push]

View File

@ -77,7 +77,8 @@
"indicator_periods_candles": [ "indicator_periods_candles": [
10, 10,
20 20
] ],
"plot_feature_importance": false
}, },
"data_split_parameters": { "data_split_parameters": {
"test_size": 0.33, "test_size": 0.33,

View File

@ -17,6 +17,7 @@ from typing import Any, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
TARGET_TRADES = 600 TARGET_TRADES = 600
@ -31,7 +32,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame], config: Config, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], backtest_stats: Dict[str, Any],
*args, **kwargs) -> float: *args, **kwargs) -> float:
""" """

View File

@ -659,17 +659,7 @@ You should also make sure to read the [Exchanges](exchanges.md) section of the d
### Using proxy with Freqtrade ### Using proxy with Freqtrade
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration. To use a proxy with freqtrade, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values.
An example for this can be found in `config_examples/config_full.example.json`
``` json
"ccxt_async_config": {
"aiohttp_trust_env": true
}
```
Then, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values
``` bash ``` bash
export HTTP_PROXY="http://addr:port" export HTTP_PROXY="http://addr:port"
@ -677,6 +667,20 @@ export HTTPS_PROXY="http://addr:port"
freqtrade freqtrade
``` ```
#### Proxy just exchange requests
To use a proxy just for exchange connections (skips/ignores telegram and coingecko) - you can also define the proxies as part of the ccxt configuration.
``` json
"ccxt_config": {
"aiohttp_proxy": "http://addr:port",
"proxies": {
"http": "http://addr:port",
"https": "http://addr:port"
},
}
```
## Next step ## Next step
Now you have configured your config.json, the next step is to [start your bot](bot-usage.md). Now you have configured your config.json, the next step is to [start your bot](bot-usage.md).

View File

@ -25,8 +25,7 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--include-inactive-pairs] [--include-inactive-pairs]
[--timerange TIMERANGE] [--dl-trades] [--timerange TIMERANGE] [--dl-trades]
[--exchange EXCHANGE] [--exchange EXCHANGE]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]] [-t TIMEFRAMES [TIMEFRAMES ...]] [--erase]
[--erase]
[--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-ohlcv {json,jsongz,hdf5}]
[--data-format-trades {json,jsongz,hdf5}] [--data-format-trades {json,jsongz,hdf5}]
[--trading-mode {spot,margin,futures}] [--trading-mode {spot,margin,futures}]
@ -37,7 +36,8 @@ optional arguments:
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
--pairs-file FILE File containing a list of pairs to download. --pairs-file FILE File containing a list of pairs. Takes precedence over
--pairs or pairs configured in the configuration.
--days INT Download data for given number of days. --days INT Download data for given number of days.
--new-pairs-days INT Download data of new pairs for given number of days. --new-pairs-days INT Download data of new pairs for given number of days.
Default: `None`. Default: `None`.
@ -50,7 +50,7 @@ optional arguments:
as --timeframes/-t. as --timeframes/-t.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided. config is provided.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...] -t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated Specify which tickers to download. Space-separated
list. Default: `1m 5m`. list. Default: `1m 5m`.
--erase Clean all existing data for the selected --erase Clean all existing data for the selected
@ -61,7 +61,7 @@ optional arguments:
--data-format-trades {json,jsongz,hdf5} --data-format-trades {json,jsongz,hdf5}
Storage format for downloaded trades data. (default: Storage format for downloaded trades data. (default:
`jsongz`). `jsongz`).
--trading-mode {spot,margin,futures} --trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode Select Trading mode
--prepend Allow data prepending. (Data-appending is disabled) --prepend Allow data prepending. (Data-appending is disabled)

View File

@ -57,7 +57,8 @@ This configuration enables kraken, as well as rate-limiting to avoid bans from t
Binance supports [time_in_force](configuration.md#understand-order_time_in_force). Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
!!! Tip "Stoploss on Exchange" !!! Tip "Stoploss on Exchange"
Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
On futures, Binance supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
### Binance Blacklist ### Binance Blacklist
@ -232,7 +233,7 @@ OKX requires a passphrase for each api key, you will therefore need to add this
!!! Warning "Futures" !!! Warning "Futures"
OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode). OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode).
Freqtrade supports both modes - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. Freqtrade supports both modes (we recommend to use net mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades.
OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data. OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data.
## Gate.io ## Gate.io

View File

@ -109,7 +109,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `indicator_max_period_candles` | **No longer used**. User must use the strategy set `startup_candle_count` which defines the maximum *period* used in `populate_any_indicators()` for indicator creation (timeframe independent). FreqAI uses this information in combination with the maximum timeframe to calculate how many data points it should download so that the first data point does not have a NaN <br> **Datatype:** positive integer. | `indicator_max_period_candles` | **No longer used**. User must use the strategy set `startup_candle_count` which defines the maximum *period* used in `populate_any_indicators()` for indicator creation (timeframe independent). FreqAI uses this information in combination with the maximum timeframe to calculate how many data points it should download so that the first data point does not have a NaN <br> **Datatype:** positive integer.
| `indicator_periods_candles` | Calculate indicators for `indicator_periods_candles` time periods and add them to the feature set. <br> **Datatype:** List of positive integers. | `indicator_periods_candles` | Calculate indicators for `indicator_periods_candles` time periods and add them to the feature set. <br> **Datatype:** List of positive integers.
| `stratify_training_data` | This value is used to indicate the grouping of the data. For example, 2 would set every 2nd data point into a separate dataset to be pulled from during training/testing. See details about how it works [here](#stratifying-the-data-for-training-and-testing-the-model) <br> **Datatype:** Positive integer. | `stratify_training_data` | This value is used to indicate the grouping of the data. For example, 2 would set every 2nd data point into a separate dataset to be pulled from during training/testing. See details about how it works [here](#stratifying-the-data-for-training-and-testing-the-model) <br> **Datatype:** Positive integer.
| `principal_component_analysis` | Automatically reduce the dimensionality of the data set using Principal Component Analysis. See details about how it works [here](#reducing-data-dimensionality-with-principal-component-analysis) <br> **Datatype:** Boolean. | `principal_component_analysis` | Automatically reduce the dimensionality of the data set using Principal Component Analysis. See details about how it works [here](#reducing-data-dimensionality-with-principal-component-analysis)
| `plot_feature_importance` | Create an interactive feature importance plot for each model.<br> **Datatype:** Boolean.<br> **Datatype:** Boolean, defaults to `False`
| `DI_threshold` | Activates the Dissimilarity Index for outlier detection when > 0. See details about how it works [here](#removing-outliers-with-the-dissimilarity-index). <br> **Datatype:** Positive float (typically < 1). | `DI_threshold` | Activates the Dissimilarity Index for outlier detection when > 0. See details about how it works [here](#removing-outliers-with-the-dissimilarity-index). <br> **Datatype:** Positive float (typically < 1).
| `use_SVM_to_remove_outliers` | Train a support vector machine to detect and remove outliers from the training data set, as well as from incoming data points. See details about how it works [here](#removing-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Boolean. | `use_SVM_to_remove_outliers` | Train a support vector machine to detect and remove outliers from the training data set, as well as from incoming data points. See details about how it works [here](#removing-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Boolean.
| `svm_params` | All parameters available in Sklearn's `SGDOneClassSVM()`. See details about some select parameters [here](#removing-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Dictionary. | `svm_params` | All parameters available in Sklearn's `SGDOneClassSVM()`. See details about some select parameters [here](#removing-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Dictionary.
@ -200,19 +201,6 @@ The FreqAI strategy requires the user to include the following lines of code in
# passed to any single indicator) # passed to any single indicator)
startup_candle_count: int = 20 startup_candle_count: int = 20
def informative_pairs(self):
whitelist_pairs = self.dp.current_whitelist()
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
informative_pairs = []
for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
for pair in whitelist_pairs:
informative_pairs.append((pair, tf))
for pair in corr_pairs:
if pair in whitelist_pairs:
continue # avoid duplication
informative_pairs.append((pair, tf))
return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# the model will return all labels created by user in `populate_any_indicators` # the model will return all labels created by user in `populate_any_indicators`

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.3.1 mkdocs==1.3.1
mkdocs-material==8.4.2 mkdocs-material==8.5.2
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.5 pymdown-extensions==9.5
jinja2==3.1.2 jinja2==3.1.2

View File

@ -264,7 +264,8 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
### Exit signal rules ### Exit signal rules
Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy. Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy.
Please note that the exit-signal is only used if `use_exit_signal` is set to true in the configuration. The exit-signal is only used for exits if `use_exit_signal` is set to true in the configuration.
`use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.

View File

@ -82,6 +82,8 @@ Example configuration showing the different settings:
"warning": "on", "warning": "on",
"startup": "off", "startup": "off",
"entry": "silent", "entry": "silent",
"entry_fill": "on",
"entry_cancel": "silent",
"exit": { "exit": {
"roi": "silent", "roi": "silent",
"emergency_exit": "on", "emergency_exit": "on",
@ -93,9 +95,7 @@ Example configuration showing the different settings:
"custom_exit": "silent", "custom_exit": "silent",
"partial_exit": "on" "partial_exit": "on"
}, },
"entry_cancel": "silent",
"exit_cancel": "on", "exit_cancel": "on",
"entry_fill": "off",
"exit_fill": "off", "exit_fill": "off",
"protection_trigger": "off", "protection_trigger": "off",
"protection_trigger_global": "on", "protection_trigger_global": "on",

View File

@ -525,12 +525,14 @@ Requires a configuration with specified `pairlists` attribute.
Can be used to generate static pairlists to be used during backtesting / hyperopt. Can be used to generate static pairlists to be used during backtesting / hyperopt.
``` ```
usage: freqtrade test-pairlist [-h] [-v] [-c PATH] usage: freqtrade test-pairlist [-h] [--userdir PATH] [-v] [-c PATH]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-1] [--print-json] [--exchange EXCHANGE] [-1] [--print-json] [--exchange EXCHANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: Specify configuration file (default:

View File

@ -53,8 +53,8 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all", "print_csv", "base_currencies", "quote_currencies", "list_pairs_all",
"trading_mode"] "trading_mode"]
ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", ARGS_TEST_PAIRLIST = ["user_data_dir", "verbosity", "config", "quote_currencies",
"list_pairs_print_json", "exchange"] "print_one_column", "list_pairs_print_json", "exchange"]
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
@ -62,9 +62,9 @@ ARGS_BUILD_CONFIG = ["config"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"] ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "exchange", "trading_mode", ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode",
"candle_types"] "candle_types"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"] ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]

View File

@ -69,7 +69,7 @@ AVAILABLE_CLI_OPTIONS = {
metavar='PATH', metavar='PATH',
), ),
"datadir": Arg( "datadir": Arg(
'-d', '--datadir', '-d', '--datadir', '--data-dir',
help='Path to directory with historical backtesting data.', help='Path to directory with historical backtesting data.',
metavar='PATH', metavar='PATH',
), ),
@ -393,7 +393,8 @@ AVAILABLE_CLI_OPTIONS = {
# Download data # Download data
"pairs_file": Arg( "pairs_file": Arg(
'--pairs-file', '--pairs-file',
help='File containing a list of pairs to download.', help='File containing a list of pairs. '
'Takes precedence over --pairs or pairs configured in the configuration.',
metavar='FILE', metavar='FILE',
), ),
"days": Arg( "days": Arg(

View File

@ -36,24 +36,24 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
""" """
fallback = 'full' fallback = 'full'
indicators = render_template_with_fallback( indicators = render_template_with_fallback(
templatefile=f"subtemplates/indicators_{subtemplate}.j2", templatefile=f"strategy_subtemplates/indicators_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/indicators_{fallback}.j2",
) )
buy_trend = render_template_with_fallback( buy_trend = render_template_with_fallback(
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2", templatefile=f"strategy_subtemplates/buy_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/buy_trend_{fallback}.j2",
) )
sell_trend = render_template_with_fallback( sell_trend = render_template_with_fallback(
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2", templatefile=f"strategy_subtemplates/sell_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/sell_trend_{fallback}.j2",
) )
plot_config = render_template_with_fallback( plot_config = render_template_with_fallback(
templatefile=f"subtemplates/plot_config_{subtemplate}.j2", templatefile=f"strategy_subtemplates/plot_config_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/plot_config_{fallback}.j2",
) )
additional_methods = render_template_with_fallback( additional_methods = render_template_with_fallback(
templatefile=f"subtemplates/strategy_methods_{subtemplate}.j2", templatefile=f"strategy_subtemplates/strategy_methods_{subtemplate}.j2",
templatefallbackfile="subtemplates/strategy_methods_empty.j2", templatefallbackfile="strategy_subtemplates/strategy_methods_empty.j2",
) )
strategy_text = render_template(templatefile='base_strategy.py.j2', strategy_text = render_template(templatefile='base_strategy.py.j2',

View File

@ -1,6 +1,6 @@
import logging import logging
from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt, from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
@ -10,7 +10,7 @@ from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool: def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
""" """
Check if the exchange name in the config file is supported by Freqtrade Check if the exchange name in the config file is supported by Freqtrade
:param check_for_bad: if True, check the exchange against the list of known 'bad' :param check_for_bad: if True, check the exchange against the list of known 'bad'

View File

@ -13,6 +13,7 @@ from freqtrade.configuration.deprecated_settings import process_temporary_deprec
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
from freqtrade.configuration.environment_vars import enironment_vars_to_dict from freqtrade.configuration.environment_vars import enironment_vars_to_dict
from freqtrade.configuration.load_config import load_file, load_from_files from freqtrade.configuration.load_config import load_file, load_from_files
from freqtrade.constants import Config
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
@ -30,10 +31,10 @@ class Configuration:
def __init__(self, args: Dict[str, Any], runmode: RunMode = None) -> None: def __init__(self, args: Dict[str, Any], runmode: RunMode = None) -> None:
self.args = args self.args = args
self.config: Optional[Dict[str, Any]] = None self.config: Optional[Config] = None
self.runmode = runmode self.runmode = runmode
def get_config(self) -> Dict[str, Any]: def get_config(self) -> Config:
""" """
Return the config. Use this method to get the bot config Return the config. Use this method to get the bot config
:return: Dict: Bot config :return: Dict: Bot config
@ -65,7 +66,7 @@ class Configuration:
:return: Configuration dictionary :return: Configuration dictionary
""" """
# Load all configs # Load all configs
config: Dict[str, Any] = load_from_files(self.args.get("config", [])) config: Config = load_from_files(self.args.get("config", []))
# Load environment variables # Load environment variables
env_data = enironment_vars_to_dict() env_data = enironment_vars_to_dict()
@ -108,7 +109,7 @@ class Configuration:
return config return config
def _process_logging_options(self, config: Dict[str, Any]) -> None: def _process_logging_options(self, config: Config) -> None:
""" """
Extract information for sys.argv and load logging configuration: Extract information for sys.argv and load logging configuration:
the -v/--verbose, --logfile options the -v/--verbose, --logfile options
@ -121,7 +122,7 @@ class Configuration:
setup_logging(config) setup_logging(config)
def _process_trading_options(self, config: Dict[str, Any]) -> None: def _process_trading_options(self, config: Config) -> None:
if config['runmode'] not in TRADING_MODES: if config['runmode'] not in TRADING_MODES:
return return
@ -137,7 +138,7 @@ class Configuration:
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"') logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
def _process_common_options(self, config: Dict[str, Any]) -> None: def _process_common_options(self, config: Config) -> None:
# Set strategy if not specified in config and or if it's non default # Set strategy if not specified in config and or if it's non default
if self.args.get('strategy') or not config.get('strategy'): if self.args.get('strategy') or not config.get('strategy'):
@ -161,7 +162,7 @@ class Configuration:
if 'sd_notify' in self.args and self.args['sd_notify']: if 'sd_notify' in self.args and self.args['sd_notify']:
config['internals'].update({'sd_notify': True}) config['internals'].update({'sd_notify': True})
def _process_datadir_options(self, config: Dict[str, Any]) -> None: def _process_datadir_options(self, config: Config) -> None:
""" """
Extract information for sys.argv and load directory configurations Extract information for sys.argv and load directory configurations
--user-data, --datadir --user-data, --datadir
@ -195,7 +196,7 @@ class Configuration:
config['exportfilename'] = (config['user_data_dir'] config['exportfilename'] = (config['user_data_dir']
/ 'backtest_results') / 'backtest_results')
def _process_optimize_options(self, config: Dict[str, Any]) -> None: def _process_optimize_options(self, config: Config) -> None:
# This will override the strategy configuration # This will override the strategy configuration
self._args_to_config(config, argname='timeframe', self._args_to_config(config, argname='timeframe',
@ -380,7 +381,7 @@ class Configuration:
self._args_to_config(config, argname="hyperopt_ignore_missing_space", self._args_to_config(config, argname="hyperopt_ignore_missing_space",
logstring="Paramter --ignore-missing-space detected: {}") logstring="Paramter --ignore-missing-space detected: {}")
def _process_plot_options(self, config: Dict[str, Any]) -> None: def _process_plot_options(self, config: Config) -> None:
self._args_to_config(config, argname='pairs', self._args_to_config(config, argname='pairs',
logstring='Using pairs {}') logstring='Using pairs {}')
@ -432,7 +433,7 @@ class Configuration:
self._args_to_config(config, argname='show_timerange', self._args_to_config(config, argname='show_timerange',
logstring='Detected --show-timerange') logstring='Detected --show-timerange')
def _process_data_options(self, config: Dict[str, Any]) -> None: def _process_data_options(self, config: Config) -> None:
self._args_to_config(config, argname='new_pairs_days', self._args_to_config(config, argname='new_pairs_days',
logstring='Detected --new-pairs-days: {}') logstring='Detected --new-pairs-days: {}')
self._args_to_config(config, argname='trading_mode', self._args_to_config(config, argname='trading_mode',
@ -443,7 +444,7 @@ class Configuration:
self._args_to_config(config, argname='candle_types', self._args_to_config(config, argname='candle_types',
logstring='Detected --candle-types: {}') logstring='Detected --candle-types: {}')
def _process_analyze_options(self, config: Dict[str, Any]) -> None: def _process_analyze_options(self, config: Config) -> None:
self._args_to_config(config, argname='analysis_groups', self._args_to_config(config, argname='analysis_groups',
logstring='Analysis reason groups: {}') logstring='Analysis reason groups: {}')
@ -456,7 +457,7 @@ class Configuration:
self._args_to_config(config, argname='indicator_list', self._args_to_config(config, argname='indicator_list',
logstring='Analysis indicator list: {}') logstring='Analysis indicator list: {}')
def _process_runmode(self, config: Dict[str, Any]) -> None: def _process_runmode(self, config: Config) -> None:
self._args_to_config(config, argname='dry_run', self._args_to_config(config, argname='dry_run',
logstring='Parameter --dry-run detected, ' logstring='Parameter --dry-run detected, '
@ -469,7 +470,7 @@ class Configuration:
config.update({'runmode': self.runmode}) config.update({'runmode': self.runmode})
def _process_freqai_options(self, config: Dict[str, Any]) -> None: def _process_freqai_options(self, config: Config) -> None:
self._args_to_config(config, argname='freqaimodel', self._args_to_config(config, argname='freqaimodel',
logstring='Using freqaimodel class name: {}') logstring='Using freqaimodel class name: {}')
@ -479,7 +480,7 @@ class Configuration:
return return
def _args_to_config(self, config: Dict[str, Any], argname: str, def _args_to_config(self, config: Config, argname: str,
logstring: str, logfun: Optional[Callable] = None, logstring: str, logfun: Optional[Callable] = None,
deprecated_msg: Optional[str] = None) -> None: deprecated_msg: Optional[str] = None) -> None:
""" """
@ -502,7 +503,7 @@ class Configuration:
if deprecated_msg: if deprecated_msg:
warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning) warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning)
def _resolve_pairs_list(self, config: Dict[str, Any]) -> None: def _resolve_pairs_list(self, config: Config) -> None:
""" """
Helper for download script. Helper for download script.
Takes first found: Takes first found:

View File

@ -3,15 +3,16 @@ Functions to handle deprecated settings
""" """
import logging import logging
from typing import Any, Dict, Optional from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_conflicting_settings(config: Dict[str, Any], def check_conflicting_settings(config: Config,
section_old: Optional[str], name_old: str, section_old: Optional[str], name_old: str,
section_new: Optional[str], name_new: str) -> None: section_new: Optional[str], name_new: str) -> None:
section_new_config = config.get(section_new, {}) if section_new else config section_new_config = config.get(section_new, {}) if section_new else config
@ -28,7 +29,7 @@ def check_conflicting_settings(config: Dict[str, Any],
) )
def process_removed_setting(config: Dict[str, Any], def process_removed_setting(config: Config,
section1: str, name1: str, section1: str, name1: str,
section2: Optional[str], name2: str) -> None: section2: Optional[str], name2: str) -> None:
""" """
@ -47,7 +48,7 @@ def process_removed_setting(config: Dict[str, Any],
) )
def process_deprecated_setting(config: Dict[str, Any], def process_deprecated_setting(config: Config,
section_old: Optional[str], name_old: str, section_old: Optional[str], name_old: str,
section_new: Optional[str], name_new: str section_new: Optional[str], name_new: str
) -> None: ) -> None:
@ -69,7 +70,7 @@ def process_deprecated_setting(config: Dict[str, Any],
del section_old_config[name_old] del section_old_config[name_old]
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: def process_temporary_deprecated_settings(config: Config) -> None:
# Kept for future deprecated / moved settings # Kept for future deprecated / moved settings
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',

View File

@ -1,16 +1,16 @@
import logging import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Optional
from freqtrade.constants import USER_DATA_FILES from freqtrade.constants import USER_DATA_FILES, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> Path: def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data") folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir: if not datadir:

View File

@ -10,7 +10,7 @@ from typing import Any, Dict, List
import rapidjson import rapidjson
from freqtrade.constants import MINIMAL_CONFIG from freqtrade.constants import MINIMAL_CONFIG, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts from freqtrade.misc import deep_merge_dicts
@ -80,7 +80,7 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) ->
Recursively load configuration files if specified. Recursively load configuration files if specified.
Sub-files are assumed to be relative to the initial config. Sub-files are assumed to be relative to the initial config.
""" """
config: Dict[str, Any] = {} config: Config = {}
if level > 5: if level > 5:
raise OperationalException("Config loop detected.") raise OperationalException("Config loop detected.")

View File

@ -3,7 +3,7 @@
""" """
bot constants bot constants
""" """
from typing import List, Literal, Tuple from typing import Any, Dict, List, Literal, Tuple
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
@ -289,11 +289,12 @@ CONF_SCHEMA = {
'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'entry_fill': {
'entry_fill': {'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'off' 'default': 'off'
}, },
'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS, },
'exit': { 'exit': {
'type': ['string', 'object'], 'type': ['string', 'object'],
'additionalProperties': { 'additionalProperties': {
@ -301,12 +302,12 @@ CONF_SCHEMA = {
'enum': TELEGRAM_SETTING_OPTIONS 'enum': TELEGRAM_SETTING_OPTIONS
} }
}, },
'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'exit_fill': { 'exit_fill': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on' 'default': 'on'
}, },
'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'protection_trigger': { 'protection_trigger': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
@ -315,14 +316,17 @@ CONF_SCHEMA = {
'protection_trigger_global': { 'protection_trigger_global': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on'
}, },
'show_candle': { 'show_candle': {
'type': 'string', 'type': 'string',
'enum': ['off', 'ohlc'], 'enum': ['off', 'ohlc'],
'default': 'off'
}, },
'strategy_msg': { 'strategy_msg': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on'
}, },
} }
}, },
@ -504,6 +508,7 @@ CONF_SCHEMA = {
"weight_factor": {"type": "number", "default": 0}, "weight_factor": {"type": "number", "default": 0},
"principal_component_analysis": {"type": "boolean", "default": False}, "principal_component_analysis": {"type": "boolean", "default": False},
"use_SVM_to_remove_outliers": {"type": "boolean", "default": False}, "use_SVM_to_remove_outliers": {"type": "boolean", "default": False},
"plot_feature_importance": {"type": "boolean", "default": False},
"svm_params": {"type": "object", "svm_params": {"type": "object",
"properties": { "properties": {
"shuffle": {"type": "boolean", "default": False}, "shuffle": {"type": "boolean", "default": False},
@ -600,3 +605,5 @@ LongShort = Literal['long', 'short']
EntryExit = Literal['entry', 'exit'] EntryExit = Literal['entry', 'exit']
BuySell = Literal['buy', 'sell'] BuySell = Literal['buy', 'sell']
MakerTaker = Literal['maker', 'taker'] MakerTaker = Literal['maker', 'taker']
Config = Dict[str, Any]

View File

@ -5,12 +5,12 @@ import itertools
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from operator import itemgetter from operator import itemgetter
from typing import Any, Dict, List from typing import Dict, List
import pandas as pd import pandas as pd
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, Config, TradeList
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
@ -237,7 +237,7 @@ def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame:
return df_new.loc[:, DEFAULT_DATAFRAME_COLUMNS] return df_new.loc[:, DEFAULT_DATAFRAME_COLUMNS]
def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool): def convert_trades_format(config: Config, convert_from: str, convert_to: str, erase: bool):
""" """
Convert trades from one format to another format. Convert trades from one format to another format.
:param config: Config dictionary :param config: Config dictionary
@ -263,7 +263,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to:
def convert_ohlcv_format( def convert_ohlcv_format(
config: Dict[str, Any], config: Config,
convert_from: str, convert_from: str,
convert_to: str, convert_to: str,
erase: bool, erase: bool,
@ -292,6 +292,7 @@ def convert_ohlcv_format(
timeframe, timeframe,
candle_type=candle_type candle_type=candle_type
)) ))
config['pairs'] = sorted(set(config['pairs']))
logger.info(f"Converting candle (OHLCV) data for {config['pairs']}") logger.info(f"Converting candle (OHLCV) data for {config['pairs']}")
for timeframe in timeframes: for timeframe in timeframes:
@ -302,7 +303,7 @@ def convert_ohlcv_format(
drop_incomplete=False, drop_incomplete=False,
startup_candles=0, startup_candles=0,
candle_type=candle_type) candle_type=candle_type)
logger.info(f"Converting {len(data)} {candle_type} candles for {pair}") logger.info(f"Converting {len(data)} {timeframe} {candle_type} candles for {pair}")
if len(data) > 0: if len(data) > 0:
trg.ohlcv_store( trg.ohlcv_store(
pair=pair, pair=pair,

View File

@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.constants import Config, ListPairsWithTimeframes, PairWithTimeframe
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
from freqtrade.enums import CandleType, RunMode from freqtrade.enums import CandleType, RunMode
from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exceptions import ExchangeError, OperationalException
@ -28,7 +28,7 @@ MAX_DATAFRAME_CANDLES = 1000
class DataProvider: class DataProvider:
def __init__(self, config: dict, exchange: Optional[Exchange], pairlists=None) -> None: def __init__(self, config: Config, exchange: Optional[Exchange], pairlists=None) -> None:
self._config = config self._config = config
self._exchange = exchange self._exchange = exchange
self._pairlists = pairlists self._pairlists = pairlists
@ -86,7 +86,7 @@ class DataProvider:
""" """
_candle_type = CandleType.from_string( _candle_type = CandleType.from_string(
candle_type) if candle_type != '' else self._config['candle_type_def'] candle_type) if candle_type != '' else self._config['candle_type_def']
saved_pair = (pair, str(timeframe), _candle_type) saved_pair: PairWithTimeframe = (pair, str(timeframe), _candle_type)
if saved_pair not in self.__cached_pairs_backtesting: if saved_pair not in self.__cached_pairs_backtesting:
timerange = TimeRange.parse_timerange(None if self._config.get( timerange = TimeRange.parse_timerange(None if self._config.get(
'timerange') is None else str(self._config.get('timerange'))) 'timerange') is None else str(self._config.get('timerange')))
@ -196,7 +196,9 @@ class DataProvider:
Clear pair dataframe cache. Clear pair dataframe cache.
""" """
self.__cached_pairs = {} self.__cached_pairs = {}
self.__cached_pairs_backtesting = {} # Don't reset backtesting pairs -
# otherwise they're reloaded each time during hyperopt due to with analyze_per_epoch
# self.__cached_pairs_backtesting = {}
self.__slice_index = 0 self.__slice_index = 0
# Exchange functions # Exchange functions

View File

@ -1,7 +1,5 @@
import logging import logging
import re from typing import Optional
from pathlib import Path
from typing import List, Optional
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -20,26 +18,6 @@ class HDF5DataHandler(IDataHandler):
_columns = DEFAULT_DATAFRAME_COLUMNS _columns = DEFAULT_DATAFRAME_COLUMNS
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs
"""
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.h5)', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.h5")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def ohlcv_store( def ohlcv_store(
self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType) -> None: self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType) -> None:
""" """
@ -121,18 +99,6 @@ class HDF5DataHandler(IDataHandler):
""" """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-trades.h5)', p.name)
for p in datadir.glob("*trades.h5")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
""" """
Store trades data (list of Dicts) to file Store trades data (list of Dicts) to file

View File

@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
class IDataHandler(ABC): class IDataHandler(ABC):
_OHLCV_REGEX = r'^([a-zA-Z_-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)' _OHLCV_REGEX = r'^([a-zA-Z_\d-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)'
def __init__(self, datadir: Path) -> None: def __init__(self, datadir: Path) -> None:
self._datadir = datadir self._datadir = datadir
@ -61,7 +61,6 @@ class IDataHandler(ABC):
) for match in _tmp if match and len(match.groups()) > 1] ) for match in _tmp if match and len(match.groups()) > 1]
@classmethod @classmethod
@abstractmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
""" """
Returns a list of all pairs with ohlcv data available in this datadir Returns a list of all pairs with ohlcv data available in this datadir
@ -71,6 +70,15 @@ class IDataHandler(ABC):
:param candle_type: Any of the enum CandleType (must match trading mode!) :param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs :return: List of Pairs
""" """
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
ext = cls._get_file_extension()
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + f'.{ext})', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.{ext}")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
@abstractmethod @abstractmethod
def ohlcv_store( def ohlcv_store(
@ -144,13 +152,17 @@ class IDataHandler(ABC):
""" """
@classmethod @classmethod
@abstractmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]: def trades_get_pairs(cls, datadir: Path) -> List[str]:
""" """
Returns a list of all pairs for which trade data is available in this Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files :param datadir: Directory to search for ohlcv files
:return: List of Pairs :return: List of Pairs
""" """
_ext = cls._get_file_extension()
_tmp = [re.search(r'^(\S+)(?=\-trades.' + _ext + ')', p.name)
for p in datadir.glob(f"*trades.{_ext}")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
@abstractmethod @abstractmethod
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
@ -255,7 +267,7 @@ class IDataHandler(ABC):
Rebuild pair name from filename Rebuild pair name from filename
Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names. Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names.
""" """
res = re.sub(r'^(([A-Za-z]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1) res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1)
res = re.sub('_', ':', res, 1) res = re.sub('_', ':', res, 1)
return res return res

View File

@ -1,7 +1,5 @@
import logging import logging
import re from typing import Optional
from pathlib import Path
from typing import List, Optional
import numpy as np import numpy as np
from pandas import DataFrame, read_json, to_datetime from pandas import DataFrame, read_json, to_datetime
@ -23,26 +21,6 @@ class JsonDataHandler(IDataHandler):
_use_zip = False _use_zip = False
_columns = DEFAULT_DATAFRAME_COLUMNS _columns = DEFAULT_DATAFRAME_COLUMNS
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs
"""
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.json)', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.{cls._get_file_extension()}")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def ohlcv_store( def ohlcv_store(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None: self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None:
""" """
@ -119,18 +97,6 @@ class JsonDataHandler(IDataHandler):
""" """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-trades.json)', p.name)
for p in datadir.glob(f"*trades.{cls._get_file_extension()}")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
""" """
Store trades data (list of Dicts) to file Store trades data (list of Dicts) to file

View File

@ -11,7 +11,7 @@ import utils_find_1st as utf1st
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.data.history import get_timerange, load_data, refresh_data from freqtrade.data.history import get_timerange, load_data, refresh_data
from freqtrade.enums import CandleType, ExitType, RunMode from freqtrade.enums import CandleType, ExitType, RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -42,10 +42,9 @@ class Edge:
Author: https://github.com/mishaker Author: https://github.com/mishaker
""" """
config: Dict = {}
_cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs _cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None: def __init__(self, config: Config, exchange, strategy) -> None:
self.config = config self.config = config
self.exchange = exchange self.exchange = exchange

View File

@ -1,5 +1,4 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import json
import logging import logging
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@ -12,7 +11,7 @@ from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.misc import deep_merge_dicts from freqtrade.misc import deep_merge_dicts, json_load
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,7 +30,7 @@ class Binance(Exchange):
"ccxt_futures_name": "future" "ccxt_futures_name": "future"
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"stoploss_order_types": {"limit": "stop"}, "stoploss_order_types": {"limit": "limit", "market": "market"},
"tickers_have_price": False, "tickers_have_price": False,
} }
@ -48,13 +47,12 @@ class Binance(Exchange):
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
:param side: "buy" or "sell" :param side: "buy" or "sell"
""" """
order_types = ('stop_loss_limit', 'stop', 'stop_market')
ordertype = 'stop' if self.trading_mode == TradingMode.FUTURES else 'stop_loss_limit'
return ( return (
order.get('stopPrice', None) is None order.get('stopPrice', None) is None
or ( or (
order['type'] == ordertype order['type'] in order_types
and ( and (
(side == "sell" and stop_loss > float(order['stopPrice'])) or (side == "sell" and stop_loss > float(order['stopPrice'])) or
(side == "buy" and stop_loss < float(order['stopPrice'])) (side == "buy" and stop_loss < float(order['stopPrice']))
@ -201,7 +199,7 @@ class Binance(Exchange):
Path(__file__).parent / 'binance_leverage_tiers.json' Path(__file__).parent / 'binance_leverage_tiers.json'
) )
with open(leverage_tiers_path) as json_file: with open(leverage_tiers_path) as json_file:
return json.load(json_file) return json_load(json_file)
else: else:
try: try:
return self._api.fetch_leverage_tiers() return self._api.fetch_leverage_tiers()

View File

@ -81,6 +81,104 @@
} }
} }
], ],
"1000LUNC/USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 25.0,
"info": {
"bracket": "1",
"initialLeverage": "25",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "75.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "700.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5700.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11950.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386950.0"
}
}
],
"1000SHIB/BUSD": [ "1000SHIB/BUSD": [
{ {
"tier": 1.0, "tier": 1.0,
@ -1109,6 +1207,88 @@
} }
} }
], ],
"AMB/BUSD": [
{
"tier": 1.0,
"currency": "BUSD",
"minNotional": 0.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "1",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "0",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "BUSD",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "2",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "625.0"
}
},
{
"tier": 3.0,
"currency": "BUSD",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "3",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5625.0"
}
},
{
"tier": 4.0,
"currency": "BUSD",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "4",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11875.0"
}
},
{
"tier": 5.0,
"currency": "BUSD",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "5",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386875.0"
}
}
],
"ANC/BUSD": [ "ANC/BUSD": [
{ {
"tier": 1.0, "tier": 1.0,
@ -3300,13 +3480,13 @@
"tier": 6.0, "tier": 6.0,
"currency": "USDT", "currency": "USDT",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "6", "bracket": "6",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386950.0" "cum": "386950.0"
@ -4880,13 +5060,13 @@
"tier": 6.0, "tier": 6.0,
"currency": "USDT", "currency": "USDT",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "6", "bracket": "6",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386940.0" "cum": "386940.0"
@ -8333,6 +8513,104 @@
} }
} }
], ],
"FOOTBALL/USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 25.0,
"info": {
"bracket": "1",
"initialLeverage": "25",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "75.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "700.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5700.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11950.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386950.0"
}
}
],
"FTM/BUSD": [ "FTM/BUSD": [
{ {
"tier": 1.0, "tier": 1.0,
@ -12123,6 +12401,104 @@
} }
} }
], ],
"LUNA2/USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 25.0,
"info": {
"bracket": "1",
"initialLeverage": "25",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "675.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5675.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11925.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386925.0"
}
}
],
"MANA/USDT": [ "MANA/USDT": [
{ {
"tier": 1.0, "tier": 1.0,
@ -13028,10 +13404,10 @@
"minNotional": 0.0, "minNotional": 0.0,
"maxNotional": 5000.0, "maxNotional": 5000.0,
"maintenanceMarginRate": 0.01, "maintenanceMarginRate": 0.01,
"maxLeverage": 50.0, "maxLeverage": 25.0,
"info": { "info": {
"bracket": "1", "bracket": "1",
"initialLeverage": "50", "initialLeverage": "25",
"notionalCap": "5000", "notionalCap": "5000",
"notionalFloor": "0", "notionalFloor": "0",
"maintMarginRatio": "0.01", "maintMarginRatio": "0.01",
@ -13805,6 +14181,88 @@
} }
} }
], ],
"PHB/BUSD": [
{
"tier": 1.0,
"currency": "BUSD",
"minNotional": 0.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "1",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "0",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "BUSD",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "2",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "625.0"
}
},
{
"tier": 3.0,
"currency": "BUSD",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "3",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5625.0"
}
},
{
"tier": 4.0,
"currency": "BUSD",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "4",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11875.0"
}
},
{
"tier": 5.0,
"currency": "BUSD",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "5",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386875.0"
}
}
],
"QTUM/USDT": [ "QTUM/USDT": [
{ {
"tier": 1.0, "tier": 1.0,
@ -14008,10 +14466,10 @@
"minNotional": 0.0, "minNotional": 0.0,
"maxNotional": 5000.0, "maxNotional": 5000.0,
"maintenanceMarginRate": 0.01, "maintenanceMarginRate": 0.01,
"maxLeverage": 50.0, "maxLeverage": 25.0,
"info": { "info": {
"bracket": "1", "bracket": "1",
"initialLeverage": "50", "initialLeverage": "25",
"notionalCap": "5000", "notionalCap": "5000",
"notionalFloor": "0", "notionalFloor": "0",
"maintMarginRatio": "0.01", "maintMarginRatio": "0.01",
@ -14478,13 +14936,13 @@
"tier": 6.0, "tier": 6.0,
"currency": "USDT", "currency": "USDT",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "6", "bracket": "6",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386950.0" "cum": "386950.0"
@ -14576,13 +15034,13 @@
"tier": 6.0, "tier": 6.0,
"currency": "USDT", "currency": "USDT",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "6", "bracket": "6",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386950.0" "cum": "386950.0"
@ -15487,6 +15945,104 @@
} }
} }
], ],
"SPELL/USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 25.0,
"info": {
"bracket": "1",
"initialLeverage": "25",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "75.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "700.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5700.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11950.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386950.0"
}
}
],
"SRM/USDT": [ "SRM/USDT": [
{ {
"tier": 1.0, "tier": 1.0,
@ -15585,6 +16141,104 @@
} }
} }
], ],
"STG/USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 25.0,
"info": {
"bracket": "1",
"initialLeverage": "25",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "75.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "700.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5700.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11950.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386950.0"
}
}
],
"STMX/USDT": [ "STMX/USDT": [
{ {
"tier": 1.0, "tier": 1.0,
@ -16176,13 +16830,13 @@
"tier": 5.0, "tier": 5.0,
"currency": "BUSD", "currency": "BUSD",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "5", "bracket": "5",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386875.0" "cum": "386875.0"
@ -16470,13 +17124,13 @@
"tier": 6.0, "tier": 6.0,
"currency": "USDT", "currency": "USDT",
"minNotional": 1000000.0, "minNotional": 1000000.0,
"maxNotional": 30000000.0, "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5, "maintenanceMarginRate": 0.5,
"maxLeverage": 1.0, "maxLeverage": 1.0,
"info": { "info": {
"bracket": "6", "bracket": "6",
"initialLeverage": "1", "initialLeverage": "1",
"notionalCap": "30000000", "notionalCap": "5000000",
"notionalFloor": "1000000", "notionalFloor": "1000000",
"maintMarginRatio": "0.5", "maintMarginRatio": "0.5",
"cum": "386950.0" "cum": "386950.0"

View File

@ -21,7 +21,8 @@ from dateutil import parser
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell, from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell,
EntryExit, ListPairsWithTimeframes, MakerTaker, PairWithTimeframe) Config, EntryExit, ListPairsWithTimeframes, MakerTaker,
PairWithTimeframe)
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
@ -91,7 +92,7 @@ class Exchange:
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
] ]
def __init__(self, config: Dict[str, Any], validate: bool = True, def __init__(self, config: Config, validate: bool = True,
load_leverage_tiers: bool = False) -> None: load_leverage_tiers: bool = False) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
@ -108,7 +109,7 @@ class Exchange:
self._loop_lock = Lock() self._loop_lock = Lock()
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
self._config: Dict = {} self._config: Config = {}
self._config.update(config) self._config.update(config)
@ -2304,7 +2305,7 @@ class Exchange:
updated = tiers.get('updated') updated = tiers.get('updated')
if updated: if updated:
updated_dt = parser.parse(updated) updated_dt = parser.parse(updated)
if updated_dt < datetime.now(timezone.utc) - timedelta(days=1): if updated_dt < datetime.now(timezone.utc) - timedelta(weeks=4):
logger.info("Cached leverage tiers are outdated. Will update.") logger.info("Cached leverage tiers are outdated. Will update.")
return None return None
return tiers['data'] return tiers['data']

View File

@ -71,6 +71,7 @@ class Okx(Exchange):
try: try:
if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
accounts = self._api.fetch_accounts() accounts = self._api.fetch_accounts()
self._log_exchange_response('fetch_accounts', accounts)
if len(accounts) > 0: if len(accounts) > 0:
self.net_only = accounts[0].get('info', {}).get('posMode') == 'net_mode' self.net_only = accounts[0].get('info', {}).get('posMode') == 'net_mode'
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:

View File

@ -16,6 +16,7 @@ from numpy.typing import NDArray
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
@ -27,9 +28,7 @@ logger = logging.getLogger(__name__)
class pair_info(TypedDict): class pair_info(TypedDict):
model_filename: str model_filename: str
first: bool
trained_timestamp: int trained_timestamp: int
priority: int
data_path: str data_path: str
extras: dict extras: dict
@ -58,7 +57,7 @@ class FreqaiDataDrawer:
Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert
""" """
def __init__(self, full_path: Path, config: dict, follow_mode: bool = False): def __init__(self, full_path: Path, config: Config, follow_mode: bool = False):
self.config = config self.config = config
self.freqai_info = config.get("freqai", {}) self.freqai_info = config.get("freqai", {})
@ -91,7 +90,7 @@ class FreqaiDataDrawer:
self.old_DBSCAN_eps: Dict[str, float] = {} self.old_DBSCAN_eps: Dict[str, float] = {}
self.empty_pair_dict: pair_info = { self.empty_pair_dict: pair_info = {
"model_filename": "", "trained_timestamp": 0, "model_filename": "", "trained_timestamp": 0,
"priority": 1, "first": True, "data_path": "", "extras": {}} "data_path": "", "extras": {}}
self.limit_ram_use = self.freqai_info.get('limit_ram_usage', False) self.limit_ram_use = self.freqai_info.get('limit_ram_usage', False)
def load_drawer_from_disk(self): def load_drawer_from_disk(self):
@ -217,7 +216,6 @@ class FreqaiDataDrawer:
self.pair_dict[pair] = self.empty_pair_dict.copy() self.pair_dict[pair] = self.empty_pair_dict.copy()
model_filename = "" model_filename = ""
trained_timestamp = 0 trained_timestamp = 0
self.pair_dict[pair]["priority"] = len(self.pair_dict)
if not data_path_set and self.follow_mode: if not data_path_set and self.follow_mode:
logger.warning( logger.warning(
@ -237,18 +235,9 @@ class FreqaiDataDrawer:
return return
else: else:
self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy() self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy()
self.pair_dict[metadata["pair"]]["priority"] = len(self.pair_dict)
return return
def pair_to_end_of_training_queue(self, pair: str) -> None:
# march all pairs up in the queue
with self.pair_dict_lock:
for p in self.pair_dict:
self.pair_dict[p]["priority"] -= 1
# send pair to end of queue
self.pair_dict[pair]["priority"] = len(self.pair_dict)
def set_initial_return_values(self, pair: str, pred_df: DataFrame) -> None: def set_initial_return_values(self, pair: str, pred_df: DataFrame) -> None:
""" """
Set the initial return values to the historical predictions dataframe. This avoids needing Set the initial return values to the historical predictions dataframe. This avoids needing
@ -356,7 +345,7 @@ class FreqaiDataDrawer:
for dir in model_folders: for dir in model_folders:
result = pattern.match(str(dir.name)) result = pattern.match(str(dir.name))
if result is None: if result is None:
break continue
coin = result.group(1) coin = result.group(1)
timestamp = result.group(2) timestamp = result.group(2)

View File

@ -18,6 +18,7 @@ from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors from sklearn.neighbors import NearestNeighbors
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@ -57,7 +58,7 @@ class FreqaiDataKitchen:
def __init__( def __init__(
self, self,
config: Dict[str, Any], config: Config,
live: bool = False, live: bool = False,
pair: str = "", pair: str = "",
): ):
@ -774,12 +775,22 @@ class FreqaiDataKitchen:
def compute_inlier_metric(self, set_='train') -> None: def compute_inlier_metric(self, set_='train') -> None:
""" """
Compute inlier metric from backwards distance distributions. Compute inlier metric from backwards distance distributions.
This metric defines how well features from a timepoint fit This metric defines how well features from a timepoint fit
into previous timepoints. into previous timepoints.
""" """
def normalise(dataframe: DataFrame, key: str) -> DataFrame:
if set_ == 'train':
min_value = dataframe.min()
max_value = dataframe.max()
self.data[f'{key}_min'] = min_value
self.data[f'{key}_max'] = max_value
else:
min_value = self.data[f'{key}_min']
max_value = self.data[f'{key}_max']
return (dataframe - min_value) / (max_value - min_value)
no_prev_pts = self.freqai_config["feature_parameters"]["inlier_metric_window"] no_prev_pts = self.freqai_config["feature_parameters"]["inlier_metric_window"]
if set_ == 'train': if set_ == 'train':
@ -824,7 +835,12 @@ class FreqaiDataKitchen:
inliers = pd.DataFrame(index=distances.index) inliers = pd.DataFrame(index=distances.index)
for key in distances.keys(): for key in distances.keys():
current_distances = distances[key].dropna() current_distances = distances[key].dropna()
current_distances = normalise(current_distances, key)
if set_ == 'train':
fit_params = stats.weibull_min.fit(current_distances) fit_params = stats.weibull_min.fit(current_distances)
self.data[f'{key}_fit_params'] = fit_params
else:
fit_params = self.data[f'{key}_fit_params']
quantiles = stats.weibull_min.cdf(current_distances, *fit_params) quantiles = stats.weibull_min.cdf(current_distances, *fit_params)
df_inlier = pd.DataFrame( df_inlier = pd.DataFrame(

View File

@ -3,6 +3,7 @@ import shutil
import threading import threading
import time import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from threading import Lock from threading import Lock
@ -14,12 +15,13 @@ from numpy.typing import NDArray
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT, Config
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.freqai.data_drawer import FreqaiDataDrawer from freqtrade.freqai.data_drawer import FreqaiDataDrawer
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.freqai.utils import plot_feature_importance
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@ -50,7 +52,7 @@ class IFreqaiModel(ABC):
Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
self.assert_config(self.config) self.assert_config(self.config)
@ -80,6 +82,7 @@ class IFreqaiModel(ABC):
self.pair_it = 0 self.pair_it = 0
self.pair_it_train = 0 self.pair_it_train = 0
self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist")) self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist"))
self.train_queue = self._set_train_queue()
self.last_trade_database_summary: DataFrame = {} self.last_trade_database_summary: DataFrame = {}
self.current_trade_database_summary: DataFrame = {} self.current_trade_database_summary: DataFrame = {}
self.analysis_lock = Lock() self.analysis_lock = Lock()
@ -101,7 +104,7 @@ class IFreqaiModel(ABC):
return ({}) return ({})
self.strategy: Optional[IStrategy] = None self.strategy: Optional[IStrategy] = None
def assert_config(self, config: Dict[str, Any]) -> None: def assert_config(self, config: Config) -> None:
if not config.get("freqai", {}): if not config.get("freqai", {}):
raise OperationalException("No freqai parameters found in configuration file.") raise OperationalException("No freqai parameters found in configuration file.")
@ -184,12 +187,16 @@ class IFreqaiModel(ABC):
""" """
while not self._stop_event.is_set(): while not self._stop_event.is_set():
time.sleep(1) time.sleep(1)
for pair in self.config.get("exchange", {}).get("pair_whitelist"): pair = self.train_queue[0]
# ensure pair is avaialble in dp
if pair not in strategy.dp.current_whitelist():
self.train_queue.popleft()
logger.warning(f'{pair} not in current whitelist, removing from train queue.')
continue
(_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair) (_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair)
if self.dd.pair_dict[pair]["priority"] != 1:
continue
dk = FreqaiDataKitchen(self.config, self.live, pair) dk = FreqaiDataKitchen(self.config, self.live, pair)
dk.set_paths(pair, trained_timestamp) dk.set_paths(pair, trained_timestamp)
( (
@ -201,11 +208,18 @@ class IFreqaiModel(ABC):
if retrain: if retrain:
self.train_timer('start') self.train_timer('start')
try:
self.extract_data_and_train_model( self.extract_data_and_train_model(
new_trained_timerange, pair, strategy, dk, data_load_timerange new_trained_timerange, pair, strategy, dk, data_load_timerange
) )
except Exception as msg:
logger.warning(f'Training {pair} raised exception {msg}, skipping.')
self.train_timer('stop') self.train_timer('stop')
# only rotate the queue after the first has been trained.
self.train_queue.rotate(-1)
self.dd.save_historic_predictions_to_disk() self.dd.save_historic_predictions_to_disk()
def start_backtesting( def start_backtesting(
@ -561,11 +575,11 @@ class IFreqaiModel(ABC):
self.dd.pair_dict[pair]["trained_timestamp"] = new_trained_timerange.stopts self.dd.pair_dict[pair]["trained_timestamp"] = new_trained_timerange.stopts
dk.set_new_model_names(pair, new_trained_timerange) dk.set_new_model_names(pair, new_trained_timerange)
self.dd.pair_dict[pair]["first"] = False
if self.dd.pair_dict[pair]["priority"] == 1 and self.scanning:
self.dd.pair_to_end_of_training_queue(pair)
self.dd.save_data(model, pair, dk) self.dd.save_data(model, pair, dk)
if self.freqai_info["feature_parameters"].get("plot_feature_importance", False):
plot_feature_importance(model, pair, dk)
if self.freqai_info.get("purge_old_models", False): if self.freqai_info.get("purge_old_models", False):
self.dd.purge_old_models() self.dd.purge_old_models()
@ -689,6 +703,32 @@ class IFreqaiModel(ABC):
return init_model return init_model
def _set_train_queue(self):
"""
Sets train queue from existing train timestamps if they exist
otherwise it sets the train queue based on the provided whitelist.
"""
current_pairlist = self.config.get("exchange", {}).get("pair_whitelist")
if not self.dd.pair_dict:
logger.info('Set fresh train queue from whitelist. '
f'Queue: {current_pairlist}')
return deque(current_pairlist)
best_queue = deque()
pair_dict_sorted = sorted(self.dd.pair_dict.items(),
key=lambda k: k[1]['trained_timestamp'])
for pair in pair_dict_sorted:
if pair[0] in current_pairlist:
best_queue.append(pair[0])
for pair in current_pairlist:
if pair not in best_queue:
best_queue.appendleft(pair)
logger.info('Set existing queue from trained timestamps. '
f'Best approximation queue: {best_queue}')
return best_queue
# Following methods which are overridden by user made prediction models. # Following methods which are overridden by user made prediction models.
# See freqai/prediction_models/CatboostPredictionModel.py for an example. # See freqai/prediction_models/CatboostPredictionModel.py for an example.

View File

@ -0,0 +1,85 @@
import logging
from typing import Any, Dict, Tuple
import numpy as np
import numpy.typing as npt
import pandas as pd
from pandas import DataFrame
from pandas.api.types import is_integer_dtype
from sklearn.preprocessing import LabelEncoder
from xgboost import XGBClassifier
from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class XGBoostClassifier(BaseClassifierModel):
"""
User created prediction model. The class needs to override three necessary
functions, predict(), train(), fit(). The class inherits ModelHandler which
has its own DataHandler where data is held, saved, loaded, and managed.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:params:
:data_dictionary: the dictionary constructed by DataHandler to hold
all the training and test data/labels.
"""
X = data_dictionary["train_features"].to_numpy()
y = data_dictionary["train_labels"].to_numpy()[:, 0]
le = LabelEncoder()
if not is_integer_dtype(y):
y = pd.Series(le.fit_transform(y), dtype="int64")
if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) == 0:
eval_set = None
else:
test_features = data_dictionary["test_features"].to_numpy()
test_labels = data_dictionary["test_labels"].to_numpy()[:, 0]
if not is_integer_dtype(test_labels):
test_labels = pd.Series(le.transform(test_labels), dtype="int64")
eval_set = [(test_features, test_labels)]
train_weights = data_dictionary["train_weights"]
init_model = self.get_init_model(dk.pair)
model = XGBClassifier(**self.model_training_parameters)
model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights,
xgb_model=init_model)
return model
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
"""
Filter the prediction features data and predict with it.
:param: unfiltered_df: Full dataframe for the current backtest period.
:return:
:pred_df: dataframe containing the predictions
:do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
data (NaNs) or felt uncertain about data (PCA and DI index)
"""
(pred_df, dk.do_predict) = super().predict(unfiltered_df, dk, **kwargs)
le = LabelEncoder()
label = dk.label_list[0]
labels_before = list(dk.data['labels_std'].keys())
labels_after = le.fit_transform(labels_before).tolist()
pred_df[label] = le.inverse_transform(pred_df[label])
pred_df = pred_df.rename(
columns={labels_after[i]: labels_before[i] for i in range(len(labels_before))})
return (pred_df, dk.do_predict)

View File

@ -1,19 +1,25 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any
import numpy as np
import pandas as pd
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history.history_utils import refresh_backtest_ohlcv_data from freqtrade.data.history.history_utils import refresh_backtest_ohlcv_data
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.exchange.exchange import market_is_active from freqtrade.exchange.exchange import market_is_active
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def download_all_data_for_training(dp: DataProvider, config: dict) -> None: def download_all_data_for_training(dp: DataProvider, config: Config) -> None:
""" """
Called only once upon start of bot to download the necessary data for Called only once upon start of bot to download the necessary data for
populating indicators and training the model. populating indicators and training the model.
@ -47,9 +53,7 @@ def download_all_data_for_training(dp: DataProvider, config: dict) -> None:
) )
def get_required_data_timerange( def get_required_data_timerange(config: Config) -> TimeRange:
config: dict
) -> TimeRange:
""" """
Used to compute the required data download time range Used to compute the required data download time range
for auto data-download in FreqAI for auto data-download in FreqAI
@ -86,7 +90,7 @@ def get_required_data_timerange(
# Keep below for when we wish to download heterogeneously lengthed data for FreqAI. # Keep below for when we wish to download heterogeneously lengthed data for FreqAI.
# def download_all_data_for_training(dp: DataProvider, config: dict) -> None: # def download_all_data_for_training(dp: DataProvider, config: Config) -> None:
# """ # """
# Called only once upon start of bot to download the necessary data for # Called only once upon start of bot to download the necessary data for
# populating indicators and training a FreqAI model. # populating indicators and training a FreqAI model.
@ -132,3 +136,58 @@ def get_required_data_timerange(
# trading_mode=config.get("trading_mode", "spot"), # trading_mode=config.get("trading_mode", "spot"),
# prepend=config.get("prepend_data", False), # prepend=config.get("prepend_data", False),
# ) # )
def plot_feature_importance(model: Any, pair: str, dk: FreqaiDataKitchen,
count_max: int = 25) -> None:
"""
Plot Best and worst features by importance for a single sub-train.
:param model: Any = A model which was `fit` using a common library
such as catboost or lightgbm
:param pair: str = pair e.g. BTC/USD
:param dk: FreqaiDataKitchen = non-persistent data container for current coin/loop
:param count_max: int = the amount of features to be loaded per column
"""
from freqtrade.plot.plotting import go, make_subplots, store_plot_file
# Extract feature importance from model
models = {}
if 'FreqaiMultiOutputRegressor' in str(model.__class__):
for estimator, label in zip(model.estimators_, dk.label_list):
models[label] = estimator
else:
models[dk.label_list[0]] = model
for label in models:
mdl = models[label]
if "catboost.core" in str(mdl.__class__):
feature_importance = mdl.get_feature_importance()
elif "lightgbm.sklearn" or "xgb" in str(mdl.__class__):
feature_importance = mdl.feature_importances_
else:
logger.info('Model type not support for generating feature importances.')
return
# Data preparation
fi_df = pd.DataFrame({
"feature_names": np.array(dk.training_features_list),
"feature_importance": np.array(feature_importance)
})
fi_df_top = fi_df.nlargest(count_max, "feature_importance")[::-1]
fi_df_worst = fi_df.nsmallest(count_max, "feature_importance")[::-1]
# Plotting
def add_feature_trace(fig, fi_df, col):
return fig.add_trace(
go.Bar(
x=fi_df["feature_importance"],
y=fi_df["feature_names"],
orientation='h', showlegend=False
), row=1, col=col
)
fig = make_subplots(rows=1, cols=2, horizontal_spacing=0.5)
fig = add_feature_trace(fig, fi_df_top, 1)
fig = add_feature_trace(fig, fi_df_worst, 2)
fig.update_layout(title_text=f"Best and worst features by importance {pair}")
label = label.replace('&', '').replace('%', '') # escape two FreqAI specific characters
store_plot_file(fig, f"{dk.model_filename}-{label}.html", dk.data_path)

View File

@ -11,9 +11,9 @@ from typing import Any, Dict, List, Optional, Tuple
from schedule import Scheduler from schedule import Scheduler
from freqtrade import __version__, constants from freqtrade import constants
from freqtrade.configuration import validate_config_consistency from freqtrade.configuration import validate_config_consistency
from freqtrade.constants import BuySell, LongShort from freqtrade.constants import BuySell, Config, LongShort
from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge from freqtrade.edge import Edge
@ -44,7 +44,7 @@ class FreqtradeBot(LoggingMixin):
This is from here the bot start its logic. This is from here the bot start its logic.
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
""" """
Init all variables and objects the bot needs to work Init all variables and objects the bot needs to work
:param config: configuration dict, you can use Configuration.get_config() :param config: configuration dict, you can use Configuration.get_config()
@ -52,8 +52,6 @@ class FreqtradeBot(LoggingMixin):
""" """
self.active_pair_whitelist: List[str] = [] self.active_pair_whitelist: List[str] = []
logger.info('Starting freqtrade %s', __version__)
# Init bot state # Init bot state
self.state = State.STOPPED self.state = State.STOPPED
@ -596,7 +594,7 @@ class FreqtradeBot(LoggingMixin):
amount = trade.amount amount = trade.amount
if amount == 0.0: if amount == 0.0:
logger.info("Amount to sell is 0.0 due to exchange limits - not selling.") logger.info("Amount to exit is 0.0 due to exchange limits - not exiting.")
return return
remaining = (trade.amount - amount) * current_exit_rate remaining = (trade.amount - amount) * current_exit_rate
@ -923,7 +921,7 @@ class FreqtradeBot(LoggingMixin):
'stake_amount': trade.stake_amount, 'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None), 'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': order.safe_amount_after_fee if fill else order.amount, 'amount': order.safe_amount_after_fee if fill else (order.amount or trade.amount),
'open_date': trade.open_date or datetime.utcnow(), 'open_date': trade.open_date or datetime.utcnow(),
'current_rate': current_rate, 'current_rate': current_rate,
'sub_trade': sub_trade, 'sub_trade': sub_trade,
@ -1072,6 +1070,7 @@ class FreqtradeBot(LoggingMixin):
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.stoploss_order_id = str(stoploss_order['id']) trade.stoploss_order_id = str(stoploss_order['id'])
trade.stoploss_last_update = datetime.now(timezone.utc)
return True return True
except InsufficientFundsError as e: except InsufficientFundsError as e:
logger.warning(f"Unable to place stoploss order {e}.") logger.warning(f"Unable to place stoploss order {e}.")
@ -1145,10 +1144,9 @@ class FreqtradeBot(LoggingMixin):
if self.create_stoploss_order(trade=trade, stop_price=stop_price): if self.create_stoploss_order(trade=trade, stop_price=stop_price):
# The above will return False if the placement failed and the trade was force-sold. # The above will return False if the placement failed and the trade was force-sold.
# in which case the trade will be closed - which we must check below. # in which case the trade will be closed - which we must check below.
trade.stoploss_last_update = datetime.utcnow()
return False return False
# If stoploss order is canceled for some reason we add it # If stoploss order is canceled for some reason we add it again
if (trade.is_open if (trade.is_open
and stoploss_order and stoploss_order
and stoploss_order['status'] in ('canceled', 'cancelled')): and stoploss_order['status'] in ('canceled', 'cancelled')):
@ -1186,7 +1184,8 @@ class FreqtradeBot(LoggingMixin):
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side): if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
# we check if the update is necessary # we check if the update is necessary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: upd_req = datetime.now(timezone.utc) - timedelta(seconds=update_beat)
if trade.stoploss_last_update_utc and upd_req >= trade.stoploss_last_update_utc:
# cancelling the current stoploss on exchange first # cancelling the current stoploss on exchange first
logger.info(f"Cancelling current stoploss on exchange for pair {trade.pair} " logger.info(f"Cancelling current stoploss on exchange for pair {trade.pair} "
f"(orderid:{order['id']}) in order to add another one ...") f"(orderid:{order['id']}) in order to add another one ...")
@ -1598,14 +1597,14 @@ class FreqtradeBot(LoggingMixin):
# second condition is for mypy only; order will always be passed during sub trade # second condition is for mypy only; order will always be passed during sub trade
if sub_trade and order is not None: if sub_trade and order is not None:
amount = order.safe_filled if fill else order.amount amount = order.safe_filled if fill else order.amount
profit_rate = order.safe_price order_rate: float = order.safe_price
profit = trade.calc_profit(rate=profit_rate, amount=amount, open_rate=trade.open_rate) profit = trade.calc_profit(rate=order_rate, amount=amount, open_rate=trade.open_rate)
profit_ratio = trade.calc_profit_ratio(profit_rate, amount, trade.open_rate) profit_ratio = trade.calc_profit_ratio(order_rate, amount, trade.open_rate)
else: else:
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested order_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit = trade.calc_profit(rate=profit_rate) + (0.0 if fill else trade.realized_profit) profit = trade.calc_profit(rate=order_rate) + (0.0 if fill else trade.realized_profit)
profit_ratio = trade.calc_profit_ratio(profit_rate) profit_ratio = trade.calc_profit_ratio(order_rate)
amount = trade.amount amount = trade.amount
gain = "profit" if profit_ratio > 0 else "loss" gain = "profit" if profit_ratio > 0 else "loss"
@ -1618,11 +1617,12 @@ class FreqtradeBot(LoggingMixin):
'leverage': trade.leverage, 'leverage': trade.leverage,
'direction': 'Short' if trade.is_short else 'Long', 'direction': 'Short' if trade.is_short else 'Long',
'gain': gain, 'gain': gain,
'limit': profit_rate, 'limit': order_rate, # Deprecated
'order_rate': order_rate,
'order_type': order_type, 'order_type': order_type,
'amount': amount, 'amount': amount,
'open_rate': trade.open_rate, 'open_rate': trade.open_rate,
'close_rate': profit_rate, 'close_rate': order_rate,
'current_rate': current_rate, 'current_rate': current_rate,
'profit_amount': profit, 'profit_amount': profit,
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,

View File

@ -2,8 +2,8 @@ import logging
import sys import sys
from logging import Formatter from logging import Formatter
from logging.handlers import BufferingHandler, RotatingFileHandler, SysLogHandler from logging.handlers import BufferingHandler, RotatingFileHandler, SysLogHandler
from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -73,7 +73,7 @@ def setup_logging_pre() -> None:
) )
def setup_logging(config: Dict[str, Any]) -> None: def setup_logging(config: Config) -> None:
""" """
Process -v/--verbose, --logfile options Process -v/--verbose, --logfile options
""" """

View File

@ -12,6 +12,7 @@ from typing import Any, List
if sys.version_info < (3, 8): # pragma: no cover if sys.version_info < (3, 8): # pragma: no cover
sys.exit("Freqtrade requires Python version >= 3.8") sys.exit("Freqtrade requires Python version >= 3.8")
from freqtrade import __version__
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.exceptions import FreqtradeException, OperationalException from freqtrade.exceptions import FreqtradeException, OperationalException
from freqtrade.loggers import setup_logging_pre from freqtrade.loggers import setup_logging_pre
@ -34,6 +35,7 @@ def main(sysargv: List[str] = None) -> None:
# Call subcommand. # Call subcommand.
if 'func' in args: if 'func' in args:
logger.info(f'freqtrade {__version__}')
return_code = args['func'](args) return_code = args['func'](args)
else: else:
# No subcommand was issued. # No subcommand was issued.

View File

@ -15,7 +15,7 @@ from pandas import DataFrame
from freqtrade import constants from freqtrade import constants
from freqtrade.configuration import TimeRange, validate_config_consistency from freqtrade.configuration import TimeRange, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT, LongShort from freqtrade.constants import DATETIME_PRINT_FORMAT, Config, LongShort
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe
from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.converter import trim_dataframe, trim_dataframes
@ -70,7 +70,7 @@ class Backtesting:
backtesting.start() backtesting.start()
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
LoggingMixin.show_output = False LoggingMixin.show_output = False
self.config = config self.config = config
@ -812,14 +812,6 @@ class Backtesting:
return trade return trade
time_in_force = self.strategy.order_time_in_force['entry'] time_in_force = self.strategy.order_time_in_force['entry']
if not pos_adjust:
# Confirm trade entry:
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate,
time_in_force=time_in_force, current_time=current_time,
entry_tag=entry_tag, side=direction):
return trade
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
self.order_id_counter += 1 self.order_id_counter += 1
base_currency = self.exchange.get_pair_base_currency(pair) base_currency = self.exchange.get_pair_base_currency(pair)
@ -834,6 +826,15 @@ class Backtesting:
# Backcalculate actual stake amount. # Backcalculate actual stake amount.
stake_amount = amount * propose_rate / leverage stake_amount = amount * propose_rate / leverage
if not pos_adjust:
# Confirm trade entry:
if not strategy_safe_wrapper(
self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=propose_rate,
time_in_force=time_in_force, current_time=current_time,
entry_tag=entry_tag, side=direction):
return trade
is_short = (direction == 'short') is_short = (direction == 'short')
# Necessary for Margin trading. Disabled until support is enabled. # Necessary for Margin trading. Disabled until support is enabled.
# interest_rate = self.exchange.get_interest_rate() # interest_rate = self.exchange.get_interest_rate()

View File

@ -4,10 +4,10 @@
This module contains the edge backtesting interface This module contains the edge backtesting interface
""" """
import logging import logging
from typing import Any, Dict
from freqtrade import constants from freqtrade import constants
from freqtrade.configuration import TimeRange, validate_config_consistency from freqtrade.configuration import TimeRange, validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.optimize.optimize_reports import generate_edge_table from freqtrade.optimize.optimize_reports import generate_edge_table
@ -26,7 +26,7 @@ class EdgeCli:
edge.start() edge.start()
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
# Ensure using dry-run # Ensure using dry-run

View File

@ -21,7 +21,7 @@ from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_
from joblib.externals import cloudpickle from joblib.externals import cloudpickle
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
from freqtrade.data.converter import trim_dataframes from freqtrade.data.converter import trim_dataframes
from freqtrade.data.history import get_timerange from freqtrade.data.history import get_timerange
from freqtrade.enums import HyperoptState from freqtrade.enums import HyperoptState
@ -66,7 +66,7 @@ class Hyperopt:
hyperopt.start() hyperopt.start()
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
self.buy_space: List[Dimension] = [] self.buy_space: List[Dimension] = []
self.sell_space: List[Dimension] = [] self.sell_space: List[Dimension] = []
self.protection_space: List[Dimension] = [] self.protection_space: List[Dimension] = []
@ -132,7 +132,7 @@ class Hyperopt:
self.print_json = self.config.get('print_json', False) self.print_json = self.config.get('print_json', False)
@staticmethod @staticmethod
def get_lock_filename(config: Dict[str, Any]) -> str: def get_lock_filename(config: Config) -> str:
return str(config['user_data_dir'] / 'hyperopt.lock') return str(config['user_data_dir'] / 'hyperopt.lock')
@ -290,7 +290,7 @@ class Hyperopt:
# noinspection PyProtectedMember # noinspection PyProtectedMember
attr.value = params_dict[attr_name] attr.value = params_dict[attr_name]
def generate_optimizer(self, raw_params: List[Any], iteration=None) -> Dict: def generate_optimizer(self, raw_params: List[Any]) -> Dict[str, Any]:
""" """
Used Optimize function. Used Optimize function.
Called once per epoch to optimize whatever is configured. Called once per epoch to optimize whatever is configured.
@ -410,9 +410,11 @@ class Hyperopt:
model_queue_size=SKOPT_MODEL_QUEUE_SIZE, model_queue_size=SKOPT_MODEL_QUEUE_SIZE,
) )
def run_optimizer_parallel(self, parallel, asked, i) -> List: def run_optimizer_parallel(
self, parallel: Parallel, asked: List[List]) -> List[Dict[str, Any]]:
""" Start optimizer in a parallel way """
return parallel(delayed( return parallel(delayed(
wrap_non_picklable_objects(self.generate_optimizer))(v, i) for v in asked) wrap_non_picklable_objects(self.generate_optimizer))(v) for v in asked)
def _set_random_state(self, random_state: Optional[int]) -> int: def _set_random_state(self, random_state: Optional[int]) -> int:
return random_state or random.randint(1, 2**16 - 1) return random_state or random.randint(1, 2**16 - 1)
@ -491,6 +493,53 @@ class Hyperopt:
else: else:
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)] return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
def get_progressbar_widgets(self):
if self.print_colorized:
widgets = [
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
' (', progressbar.Percentage(), ')] ',
progressbar.Bar(marker=progressbar.AnimatedMarker(
fill='\N{FULL BLOCK}',
fill_wrap=Fore.GREEN + '{}' + Fore.RESET,
marker_wrap=Style.BRIGHT + '{}' + Style.RESET_ALL,
)),
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
]
else:
widgets = [
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
' (', progressbar.Percentage(), ')] ',
progressbar.Bar(marker=progressbar.AnimatedMarker(
fill='\N{FULL BLOCK}',
)),
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
]
return widgets
def evaluate_result(self, val: Dict[str, Any], current: int, is_random: bool):
"""
Evaluate results returned from generate_optimizer
"""
val['current_epoch'] = current
val['is_initial_point'] = current <= INITIAL_POINTS
logger.debug("Optimizer epoch evaluated: %s", val)
is_best = HyperoptTools.is_best_loss(val, self.current_best_loss)
# This value is assigned here and not in the optimization method
# to keep proper order in the list of results. That's because
# evaluations can take different time. Here they are aligned in the
# order they will be shown to the user.
val['is_best'] = is_best
val['is_random'] = is_random
self.print_results(val)
if is_best:
self.current_best_loss = val['loss']
self.current_best_epoch = val
self._save_result(val)
def start(self) -> None: def start(self) -> None:
self.random_state = self._set_random_state(self.config.get('hyperopt_random_state')) self.random_state = self._set_random_state(self.config.get('hyperopt_random_state'))
logger.info(f"Using optimizer random state: {self.random_state}") logger.info(f"Using optimizer random state: {self.random_state}")
@ -526,64 +575,40 @@ class Hyperopt:
logger.info(f'Effective number of parallel workers used: {jobs}') logger.info(f'Effective number of parallel workers used: {jobs}')
# Define progressbar # Define progressbar
if self.print_colorized: widgets = self.get_progressbar_widgets()
widgets = [
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
' (', progressbar.Percentage(), ')] ',
progressbar.Bar(marker=progressbar.AnimatedMarker(
fill='\N{FULL BLOCK}',
fill_wrap=Fore.GREEN + '{}' + Fore.RESET,
marker_wrap=Style.BRIGHT + '{}' + Style.RESET_ALL,
)),
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
]
else:
widgets = [
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
' (', progressbar.Percentage(), ')] ',
progressbar.Bar(marker=progressbar.AnimatedMarker(
fill='\N{FULL BLOCK}',
)),
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
]
with progressbar.ProgressBar( with progressbar.ProgressBar(
max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False, max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False,
widgets=widgets widgets=widgets
) as pbar: ) as pbar:
EVALS = ceil(self.total_epochs / jobs) start = 0
for i in range(EVALS):
if self.analyze_per_epoch:
# First analysis not in parallel mode when using --analyze-per-epoch.
# This allows dataprovider to load it's informative cache.
asked, is_random = self.get_asked_points(n_points=1)
f_val0 = self.generate_optimizer(asked[0])
self.opt.tell(asked, [f_val0['loss']])
self.evaluate_result(f_val0, 1, is_random[0])
pbar.update(1)
start += 1
evals = ceil((self.total_epochs - start) / jobs)
for i in range(evals):
# Correct the number of epochs to be processed for the last # Correct the number of epochs to be processed for the last
# iteration (should not exceed self.total_epochs in total) # iteration (should not exceed self.total_epochs in total)
n_rest = (i + 1) * jobs - self.total_epochs n_rest = (i + 1) * jobs - (self.total_epochs - start)
current_jobs = jobs - n_rest if n_rest > 0 else jobs current_jobs = jobs - n_rest if n_rest > 0 else jobs
asked, is_random = self.get_asked_points(n_points=current_jobs) asked, is_random = self.get_asked_points(n_points=current_jobs)
f_val = self.run_optimizer_parallel(parallel, asked, i) f_val = self.run_optimizer_parallel(parallel, asked)
self.opt.tell(asked, [v['loss'] for v in f_val]) self.opt.tell(asked, [v['loss'] for v in f_val])
# Calculate progressbar outputs # Calculate progressbar outputs
for j, val in enumerate(f_val): for j, val in enumerate(f_val):
# Use human-friendly indexes here (starting from 1) # Use human-friendly indexes here (starting from 1)
current = i * jobs + j + 1 current = i * jobs + j + 1 + start
val['current_epoch'] = current
val['is_initial_point'] = current <= INITIAL_POINTS
logger.debug(f"Optimizer epoch evaluated: {val}") self.evaluate_result(val, current, is_random[j])
is_best = HyperoptTools.is_best_loss(val, self.current_best_loss)
# This value is assigned here and not in the optimization method
# to keep proper order in the list of results. That's because
# evaluations can take different time. Here they are aligned in the
# order they will be shown to the user.
val['is_best'] = is_best
val['is_random'] = is_random[j]
self.print_results(val)
if is_best:
self.current_best_loss = val['loss']
self.current_best_epoch = val
self._save_result(val)
pbar.update(current) pbar.update(current)

View File

@ -10,6 +10,7 @@ from typing import Dict, List, Union
from sklearn.base import RegressorMixin from sklearn.base import RegressorMixin
from skopt.space import Categorical, Dimension, Integer from skopt.space import Categorical, Dimension, Integer
from freqtrade.constants import Config
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import round_dict from freqtrade.misc import round_dict
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
@ -32,7 +33,7 @@ class IHyperOpt(ABC):
timeframe: str timeframe: str
strategy: IStrategy strategy: IStrategy
def __init__(self, config: dict) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
# Assign timeframe to be used in hyperopt # Assign timeframe to be used in hyperopt

View File

@ -10,6 +10,7 @@ from typing import Any, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -27,7 +28,7 @@ class CalmarHyperOptLoss(IHyperOptLoss):
trade_count: int, trade_count: int,
min_date: datetime, min_date: datetime,
max_date: datetime, max_date: datetime,
config: Dict, config: Config,
processed: Dict[str, DataFrame], processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], backtest_stats: Dict[str, Any],
*args, *args,

View File

@ -4,10 +4,9 @@ MaxDrawDownRelativeHyperOptLoss
This module defines the alternative HyperOptLoss class which can be used for This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization. Hyperoptimization.
""" """
from typing import Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.data.metrics import calculate_underwater from freqtrade.data.metrics import calculate_underwater
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -22,7 +21,7 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss):
""" """
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, config: Dict, def hyperopt_loss_function(results: DataFrame, config: Config,
*args, **kwargs) -> float: *args, **kwargs) -> float:
""" """

View File

@ -9,6 +9,8 @@ from typing import Any, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
class IHyperOptLoss(ABC): class IHyperOptLoss(ABC):
""" """
@ -21,7 +23,7 @@ class IHyperOptLoss(ABC):
@abstractmethod @abstractmethod
def hyperopt_loss_function(*, results: DataFrame, trade_count: int, def hyperopt_loss_function(*, results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame], config: Config, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], backtest_stats: Dict[str, Any],
**kwargs) -> float: **kwargs) -> float:
""" """

View File

@ -12,7 +12,7 @@ import tabulate
from colorama import Fore, Style from colorama import Fore, Style
from pandas import isna, json_normalize from pandas import isna, json_normalize
from freqtrade.constants import FTHYPT_FILEVERSION, USERPATH_STRATEGIES from freqtrade.constants import FTHYPT_FILEVERSION, USERPATH_STRATEGIES, Config
from freqtrade.enums import HyperoptState from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, round_coin_value, round_dict, safe_value_fallback2 from freqtrade.misc import deep_merge_dicts, round_coin_value, round_dict, safe_value_fallback2
@ -45,7 +45,7 @@ class HyperoptStateContainer():
class HyperoptTools(): class HyperoptTools():
@staticmethod @staticmethod
def get_strategy_filename(config: Dict, strategy_name: str) -> Optional[Path]: def get_strategy_filename(config: Config, strategy_name: str) -> Optional[Path]:
""" """
Get Strategy-location (filename) from strategy_name Get Strategy-location (filename) from strategy_name
""" """
@ -81,7 +81,7 @@ class HyperoptTools():
) )
@staticmethod @staticmethod
def try_export_params(config: Dict[str, Any], strategy_name: str, params: Dict): def try_export_params(config: Config, strategy_name: str, params: Dict):
if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get('disableparamexport', False): if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get('disableparamexport', False):
# Export parameters ... # Export parameters ...
fn = HyperoptTools.get_strategy_filename(config, strategy_name) fn = HyperoptTools.get_strategy_filename(config, strategy_name)
@ -91,7 +91,7 @@ class HyperoptTools():
logger.warning("Strategy not found, not exporting parameter file.") logger.warning("Strategy not found, not exporting parameter file.")
@staticmethod @staticmethod
def has_space(config: Dict[str, Any], space: str) -> bool: def has_space(config: Config, space: str) -> bool:
""" """
Tell if the space value is contained in the configuration Tell if the space value is contained in the configuration
""" """
@ -131,7 +131,7 @@ class HyperoptTools():
return False return False
@staticmethod @staticmethod
def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[List, int]: def load_filtered_results(results_file: Path, config: Config) -> Tuple[List, int]:
filteroptions = { filteroptions = {
'only_best': config.get('hyperopt_list_best', False), 'only_best': config.get('hyperopt_list_best', False),
'only_profitable': config.get('hyperopt_list_profitable', False), 'only_profitable': config.get('hyperopt_list_profitable', False),
@ -346,7 +346,7 @@ class HyperoptTools():
return trials return trials
@staticmethod @staticmethod
def get_result_table(config: dict, results: list, total_epochs: int, highlight_best: bool, def get_result_table(config: Config, results: list, total_epochs: int, highlight_best: bool,
print_colorized: bool, remove_header: int) -> str: print_colorized: bool, remove_header: int) -> str:
""" """
Log result table Log result table
@ -444,7 +444,7 @@ class HyperoptTools():
return table return table
@staticmethod @staticmethod
def export_csv_file(config: dict, results: list, csv_file: str) -> None: def export_csv_file(config: Config, results: list, csv_file: str) -> None:
""" """
Log result to csv-file Log result to csv-file
""" """

View File

@ -7,7 +7,8 @@ from typing import Any, Dict, List, Union
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from tabulate import tabulate from tabulate import tabulate
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.constants import (DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT,
Config)
from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change, from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change,
calculate_max_drawdown) calculate_max_drawdown)
from freqtrade.misc import decimals_per_coin, file_dump_joblib, file_dump_json, round_coin_value from freqtrade.misc import decimals_per_coin, file_dump_joblib, file_dump_json, round_coin_value
@ -898,7 +899,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print() print()
def show_backtest_results(config: Dict, backtest_stats: Dict): def show_backtest_results(config: Config, backtest_stats: Dict):
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
for strategy, results in backtest_stats['strategy'].items(): for strategy, results in backtest_stats['strategy'].items():
@ -918,7 +919,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict):
print('\nFor more details, please look at the detail tables above') print('\nFor more details, please look at the detail tables above')
def show_sorted_pairlist(config: Dict, backtest_stats: Dict): def show_sorted_pairlist(config: Config, backtest_stats: Dict):
if config.get('backtest_show_pair_list', False): if config.get('backtest_show_pair_list', False):
for strategy, results in backtest_stats['strategy'].items(): for strategy, results in backtest_stats['strategy'].items():
print(f"Pairs for Strategy {strategy}: \n[") print(f"Pairs for Strategy {strategy}: \n[")

View File

@ -83,7 +83,7 @@ class Order(_DECL_BASE):
@property @property
def safe_price(self) -> float: def safe_price(self) -> float:
return self.average or self.price return self.average or self.price or self.stop_price
@property @property
def safe_filled(self) -> float: def safe_filled(self) -> float:
@ -376,6 +376,12 @@ class LocalTrade():
def open_date_utc(self): def open_date_utc(self):
return self.open_date.replace(tzinfo=timezone.utc) return self.open_date.replace(tzinfo=timezone.utc)
@property
def stoploss_last_update_utc(self):
if self.stoploss_last_update:
return self.stoploss_last_update.replace(tzinfo=timezone.utc)
return None
@property @property
def close_date_utc(self): def close_date_utc(self):
return self.close_date.replace(tzinfo=timezone.utc) return self.close_date.replace(tzinfo=timezone.utc)
@ -560,7 +566,6 @@ class LocalTrade():
self.stop_loss = stop_loss_norm self.stop_loss = stop_loss_norm
self.stop_loss_pct = -1 * abs(percent) self.stop_loss_pct = -1 * abs(percent)
self.stoploss_last_update = datetime.utcnow()
def adjust_stop_loss(self, current_price: float, stoploss: float, def adjust_stop_loss(self, current_price: float, stoploss: float,
initial: bool = False, refresh: bool = False) -> None: initial: bool = False, refresh: bool = False) -> None:

View File

@ -1,10 +1,11 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional from typing import Dict, List, Optional
import pandas as pd import pandas as pd
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (analyze_trade_parallelism, extract_trades_of_period, from freqtrade.data.btanalysis import (analyze_trade_parallelism, extract_trades_of_period,
load_trades) load_trades)
from freqtrade.data.converter import trim_dataframe from freqtrade.data.converter import trim_dataframe
@ -618,7 +619,7 @@ def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False
logger.info(f"Stored plot as {_filename}") logger.info(f"Stored plot as {_filename}")
def load_and_plot_trades(config: Dict[str, Any]): def load_and_plot_trades(config: Config):
""" """
From configuration provided From configuration provided
- Initializes plot-script - Initializes plot-script
@ -666,7 +667,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
logger.info('End of plotting process. %s plots generated', pair_counter) logger.info('End of plotting process. %s plots generated', pair_counter)
def plot_profit(config: Dict[str, Any]) -> None: def plot_profit(config: Config) -> None:
""" """
Plots the total profit for all pairs. Plots the total profit for all pairs.
Note, the profit calculation isn't realistic. Note, the profit calculation isn't realistic.

View File

@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
class AgeFilter(IPairList): class AgeFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -6,6 +6,7 @@ from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange, market_is_active from freqtrade.exchange import Exchange, market_is_active
from freqtrade.mixins import LoggingMixin from freqtrade.mixins import LoggingMixin
@ -17,7 +18,7 @@ logger = logging.getLogger(__name__)
class IPairList(LoggingMixin, ABC): class IPairList(LoggingMixin, ABC):
def __init__(self, exchange: Exchange, pairlistmanager, def __init__(self, exchange: Exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
""" """
:param exchange: Exchange instance :param exchange: Exchange instance

View File

@ -4,6 +4,7 @@ Offset pair list filter
import logging import logging
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
class OffsetFilter(IPairList): class OffsetFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -6,6 +6,7 @@ from typing import Any, Dict, List
import pandas as pd import pandas as pd
from freqtrade.constants import Config
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -16,7 +17,7 @@ logger = logging.getLogger(__name__)
class PerformanceFilter(IPairList): class PerformanceFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -4,6 +4,7 @@ Precision pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
class PrecisionFilter(IPairList): class PrecisionFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -4,6 +4,7 @@ Price pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
class PriceFilter(IPairList): class PriceFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -5,6 +5,7 @@ import logging
import random import random
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -15,7 +16,7 @@ logger = logging.getLogger(__name__)
class ShuffleFilter(IPairList): class ShuffleFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -4,6 +4,7 @@ Spread pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
class SpreadFilter(IPairList): class SpreadFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -7,6 +7,7 @@ import logging
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -16,7 +17,7 @@ logger = logging.getLogger(__name__)
class StaticPairList(IPairList): class StaticPairList(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -11,7 +11,7 @@ import numpy as np
from cachetools import TTLCache from cachetools import TTLCache
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -26,7 +26,7 @@ class VolatilityFilter(IPairList):
""" """
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -9,7 +9,7 @@ from typing import Any, Dict, List
from cachetools import TTLCache from cachetools import TTLCache
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, 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.misc import format_ms_time from freqtrade.misc import format_ms_time
@ -25,7 +25,7 @@ SORT_VALUES = ['quoteVolume']
class VolumePairList(IPairList): class VolumePairList(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -1,5 +1,7 @@
import re import re
from typing import Any, Dict, List from typing import List
from freqtrade.constants import Config
def expand_pairlist(wildcardpl: List[str], available_pairs: List[str], def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
@ -42,7 +44,7 @@ def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
return result return result
def dynamic_expand_pairlist(config: Dict[str, Any], markets: List[str]) -> List[str]: def dynamic_expand_pairlist(config: Config, markets: List[str]) -> List[str]:
expanded_pairs = expand_pairlist(config['pairs'], markets) expanded_pairs = expand_pairlist(config['pairs'], markets)
if config.get('freqai', {}).get('enabled', False): if config.get('freqai', {}).get('enabled', False):
corr_pairlist = config['freqai']['feature_parameters']['include_corr_pairlist'] corr_pairlist = config['freqai']['feature_parameters']['include_corr_pairlist']

View File

@ -9,7 +9,7 @@ import arrow
from cachetools import TTLCache from cachetools import TTLCache
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
class RangeStabilityFilter(IPairList): class RangeStabilityFilter(IPairList):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Config, pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
@ -100,23 +100,19 @@ class RangeStabilityFilter(IPairList):
if cached_res is not None: if cached_res is not None:
return cached_res return cached_res
result = False result = True
if daily_candles is not None and not daily_candles.empty: if daily_candles is not None and not daily_candles.empty:
highest_high = daily_candles['high'].max() highest_high = daily_candles['high'].max()
lowest_low = daily_candles['low'].min() lowest_low = daily_candles['low'].min()
pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0
if pct_change >= self._min_rate_of_change: if pct_change < self._min_rate_of_change:
result = True
else:
self.log_once(f"Removed {pair} from whitelist, because rate of change " self.log_once(f"Removed {pair} from whitelist, because rate of change "
f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, "
f"which is below the threshold of {self._min_rate_of_change}.", f"which is below the threshold of {self._min_rate_of_change}.",
logger.info) logger.info)
result = False result = False
if self._max_rate_of_change: if self._max_rate_of_change:
if pct_change <= self._max_rate_of_change: if pct_change > self._max_rate_of_change:
result = True
else:
self.log_once( self.log_once(
f"Removed {pair} from whitelist, because rate of change " f"Removed {pair} from whitelist, because rate of change "
f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, "

View File

@ -7,7 +7,7 @@ from typing import Dict, List
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.mixins import LoggingMixin from freqtrade.mixins import LoggingMixin
@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
class PairListManager(LoggingMixin): class PairListManager(LoggingMixin):
def __init__(self, exchange, config: dict) -> None: def __init__(self, exchange, config: Config) -> None:
self._exchange = exchange self._exchange = exchange
self._config = config self._config = config
self._whitelist = self._config['exchange'].get('pair_whitelist') self._whitelist = self._config['exchange'].get('pair_whitelist')

View File

@ -5,7 +5,7 @@ import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, List, Optional from typing import Dict, List, Optional
from freqtrade.constants import LongShort from freqtrade.constants import Config, LongShort
from freqtrade.persistence import PairLocks from freqtrade.persistence import PairLocks
from freqtrade.persistence.models import PairLock from freqtrade.persistence.models import PairLock
from freqtrade.plugins.protections import IProtection from freqtrade.plugins.protections import IProtection
@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
class ProtectionManager(): class ProtectionManager():
def __init__(self, config: Dict, protections: List) -> None: def __init__(self, config: Config, protections: List) -> None:
self._config = config self._config = config
self._protection_handlers: List[IProtection] = [] self._protection_handlers: List[IProtection] = []

View File

@ -5,7 +5,7 @@ from dataclasses import dataclass
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from freqtrade.constants import LongShort from freqtrade.constants import Config, LongShort
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.mixins import LoggingMixin from freqtrade.mixins import LoggingMixin
@ -30,7 +30,7 @@ class IProtection(LoggingMixin, ABC):
# Can stop trading for one pair # Can stop trading for one pair
has_local_stop: bool = False has_local_stop: bool = False
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None:
self._config = config self._config = config
self._protection_config = protection_config self._protection_config = protection_config
self._stop_duration_candles: Optional[int] = None self._stop_duration_candles: Optional[int] = None

View File

@ -3,7 +3,7 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from freqtrade.constants import LongShort from freqtrade.constants import Config, LongShort
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection, ProtectionReturn from freqtrade.plugins.protections import IProtection, ProtectionReturn
@ -16,7 +16,7 @@ class LowProfitPairs(IProtection):
has_global_stop: bool = False has_global_stop: bool = False
has_local_stop: bool = True has_local_stop: bool = True
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None:
super().__init__(config, protection_config) super().__init__(config, protection_config)
self._trade_limit = protection_config.get('trade_limit', 1) self._trade_limit = protection_config.get('trade_limit', 1)

View File

@ -5,7 +5,7 @@ from typing import Any, Dict, Optional
import pandas as pd import pandas as pd
from freqtrade.constants import LongShort from freqtrade.constants import Config, LongShort
from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection, ProtectionReturn from freqtrade.plugins.protections import IProtection, ProtectionReturn
@ -19,7 +19,7 @@ class MaxDrawdown(IProtection):
has_global_stop: bool = True has_global_stop: bool = True
has_local_stop: bool = False has_local_stop: bool = False
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None:
super().__init__(config, protection_config) super().__init__(config, protection_config)
self._trade_limit = protection_config.get('trade_limit', 1) self._trade_limit = protection_config.get('trade_limit', 1)

View File

@ -3,7 +3,7 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from freqtrade.constants import LongShort from freqtrade.constants import Config, LongShort
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection, ProtectionReturn from freqtrade.plugins.protections import IProtection, ProtectionReturn
@ -17,7 +17,7 @@ class StoplossGuard(IProtection):
has_global_stop: bool = True has_global_stop: bool = True
has_local_stop: bool = True has_local_stop: bool = True
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None:
super().__init__(config, protection_config) super().__init__(config, protection_config)
self._trade_limit = protection_config.get('trade_limit', 10) self._trade_limit = protection_config.get('trade_limit', 10)

View File

@ -4,6 +4,7 @@ This module loads custom exchanges
import logging import logging
import freqtrade.exchange as exchanges import freqtrade.exchange as exchanges
from freqtrade.constants import Config
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, Exchange from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, Exchange
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -18,7 +19,7 @@ class ExchangeResolver(IResolver):
object_type = Exchange object_type = Exchange
@staticmethod @staticmethod
def load_exchange(exchange_name: str, config: dict, validate: bool = True, def load_exchange(exchange_name: str, config: Config, validate: bool = True,
load_leverage_tiers: bool = False) -> Exchange: load_leverage_tiers: bool = False) -> Exchange:
""" """
Load the custom class from config parameter Load the custom class from config parameter

View File

@ -5,9 +5,8 @@ This module load a custom model for freqai
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Dict
from freqtrade.constants import USERPATH_FREQAIMODELS from freqtrade.constants import USERPATH_FREQAIMODELS, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqai.freqai_interface import IFreqaiModel from freqtrade.freqai.freqai_interface import IFreqaiModel
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -29,7 +28,7 @@ class FreqaiModelResolver(IResolver):
) )
@staticmethod @staticmethod
def load_freqaimodel(config: Dict) -> IFreqaiModel: def load_freqaimodel(config: Config) -> IFreqaiModel:
""" """
Load the custom class from config parameter Load the custom class from config parameter
:param config: configuration dictionary :param config: configuration dictionary

View File

@ -5,9 +5,8 @@ This module load custom hyperopt
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Dict
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN, USERPATH_HYPEROPTS from freqtrade.constants import HYPEROPT_LOSS_BUILTIN, USERPATH_HYPEROPTS, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -26,7 +25,7 @@ class HyperOptLossResolver(IResolver):
initial_search_path = Path(__file__).parent.parent.joinpath('optimize/hyperopt_loss').resolve() initial_search_path = Path(__file__).parent.parent.joinpath('optimize/hyperopt_loss').resolve()
@staticmethod @staticmethod
def load_hyperoptloss(config: Dict) -> IHyperOptLoss: def load_hyperoptloss(config: Config) -> IHyperOptLoss:
""" """
Load the custom class from config parameter Load the custom class from config parameter
:param config: configuration dictionary :param config: configuration dictionary

View File

@ -10,6 +10,7 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -43,7 +44,7 @@ class IResolver:
initial_search_path: Optional[Path] initial_search_path: Optional[Path]
@classmethod @classmethod
def build_search_paths(cls, config: Dict[str, Any], user_subdir: Optional[str] = None, def build_search_paths(cls, config: Config, user_subdir: Optional[str] = None,
extra_dirs: List[str] = []) -> List[Path]: extra_dirs: List[str] = []) -> List[Path]:
abs_paths: List[Path] = [] abs_paths: List[Path] = []
@ -153,7 +154,7 @@ class IResolver:
return None return None
@classmethod @classmethod
def load_object(cls, object_name: str, config: dict, *, kwargs: dict, def load_object(cls, object_name: str, config: Config, *, kwargs: dict,
extra_dir: Optional[str] = None) -> Any: extra_dir: Optional[str] = None) -> Any:
""" """
Search and loads the specified object as configured in hte child class. Search and loads the specified object as configured in hte child class.

View File

@ -6,6 +6,7 @@ This module load custom pairlists
import logging import logging
from pathlib import Path from pathlib import Path
from freqtrade.constants import Config
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -24,7 +25,7 @@ class PairListResolver(IResolver):
@staticmethod @staticmethod
def load_pairlist(pairlist_name: str, exchange, pairlistmanager, def load_pairlist(pairlist_name: str, exchange, pairlistmanager,
config: dict, pairlistconfig: dict, pairlist_pos: int) -> IPairList: config: Config, pairlistconfig: dict, pairlist_pos: int) -> IPairList:
""" """
Load the pairlist with pairlist_name Load the pairlist with pairlist_name
:param pairlist_name: Classname of the pairlist :param pairlist_name: Classname of the pairlist

View File

@ -5,6 +5,7 @@ import logging
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
from freqtrade.constants import Config
from freqtrade.plugins.protections import IProtection from freqtrade.plugins.protections import IProtection
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -22,7 +23,8 @@ class ProtectionResolver(IResolver):
initial_search_path = Path(__file__).parent.parent.joinpath('plugins/protections').resolve() initial_search_path = Path(__file__).parent.parent.joinpath('plugins/protections').resolve()
@staticmethod @staticmethod
def load_protection(protection_name: str, config: Dict, protection_config: Dict) -> IProtection: def load_protection(protection_name: str, config: Config,
protection_config: Dict) -> IProtection:
""" """
Load the protection with protection_name Load the protection with protection_name
:param protection_name: Classname of the pairlist :param protection_name: Classname of the pairlist

View File

@ -9,10 +9,10 @@ from base64 import urlsafe_b64decode
from inspect import getfullargspec from inspect import getfullargspec
from os import walk from os import walk
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional from typing import Any, List, Optional
from freqtrade.configuration.config_validation import validate_migrated_strategy_settings from freqtrade.configuration.config_validation import validate_migrated_strategy_settings
from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES, Config
from freqtrade.enums import TradingMode from freqtrade.enums import TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -32,7 +32,7 @@ class StrategyResolver(IResolver):
initial_search_path = None initial_search_path = None
@staticmethod @staticmethod
def load_strategy(config: Dict[str, Any] = None) -> IStrategy: def load_strategy(config: Config = None) -> IStrategy:
""" """
Load the custom class from config parameter Load the custom class from config parameter
:param config: configuration dictionary or None :param config: configuration dictionary or None
@ -91,8 +91,7 @@ class StrategyResolver(IResolver):
return strategy return strategy
@staticmethod @staticmethod
def _override_attribute_helper(strategy, config: Dict[str, Any], def _override_attribute_helper(strategy, config: Config, attribute: str, default: Any):
attribute: str, default: Any):
""" """
Override attributes in the strategy. Override attributes in the strategy.
Prevalence: Prevalence:
@ -215,7 +214,7 @@ class StrategyResolver(IResolver):
@staticmethod @staticmethod
def _load_strategy(strategy_name: str, def _load_strategy(strategy_name: str,
config: dict, extra_dir: Optional[str] = None) -> IStrategy: config: Config, extra_dir: Optional[str] = None) -> IStrategy:
""" """
Search and loads the specified strategy. Search and loads the specified strategy.
:param strategy_name: name of the module to import :param strategy_name: name of the module to import

View File

@ -8,6 +8,7 @@ from fastapi import Depends, FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler
@ -37,10 +38,10 @@ class ApiServer(RPCHandler):
_bt = None _bt = None
_bt_data = None _bt_data = None
_bt_timerange = None _bt_timerange = None
_bt_last_config: Dict[str, Any] = {} _bt_last_config: Config = {}
_has_rpc: bool = False _has_rpc: bool = False
_bgtask_running: bool = False _bgtask_running: bool = False
_config: Dict[str, Any] = {} _config: Config = {}
# Exchange - only available in webserver mode. # Exchange - only available in webserver mode.
_exchange = None _exchange = None
@ -54,7 +55,7 @@ class ApiServer(RPCHandler):
ApiServer.__initialized = False ApiServer.__initialized = False
return ApiServer.__instance return ApiServer.__instance
def __init__(self, config: Dict[str, Any], standalone: bool = False) -> None: def __init__(self, config: Config, standalone: bool = False) -> None:
ApiServer._config = config ApiServer._config = config
if self.__initialized and (standalone or self._standalone): if self.__initialized and (standalone or self._standalone):
return return

View File

@ -1,6 +1,6 @@
import logging import logging
from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.enums import RPCMessageType from freqtrade.enums import RPCMessageType
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.webhook import Webhook from freqtrade.rpc.webhook import Webhook
@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class Discord(Webhook): class Discord(Webhook):
def __init__(self, rpc: 'RPC', config: Dict[str, Any]): def __init__(self, rpc: 'RPC', config: Config):
# super().__init__(rpc, config) # super().__init__(rpc, config)
self.rpc = rpc self.rpc = rpc
self.config = config self.config = config

View File

@ -16,7 +16,7 @@ from pandas import DataFrame, NaT
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State,
@ -58,7 +58,7 @@ class RPCException(Exception):
class RPCHandler: class RPCHandler:
def __init__(self, rpc: 'RPC', config: Dict[str, Any]) -> None: def __init__(self, rpc: 'RPC', config: Config) -> None:
""" """
Initializes RPCHandlers Initializes RPCHandlers
:param rpc: instance of RPC Helper class :param rpc: instance of RPC Helper class
@ -66,7 +66,7 @@ class RPCHandler:
:return: None :return: None
""" """
self._rpc = rpc self._rpc = rpc
self._config: Dict[str, Any] = config self._config: Config = config
@property @property
def name(self) -> str: def name(self) -> str:
@ -96,7 +96,7 @@ class RPC:
:return: None :return: None
""" """
self._freqtrade = freqtrade self._freqtrade = freqtrade
self._config: Dict[str, Any] = freqtrade.config self._config: Config = freqtrade.config
if self._config.get('fiat_display_currency'): if self._config.get('fiat_display_currency'):
self._fiat_converter = CryptoToFiatConverter() self._fiat_converter = CryptoToFiatConverter()

View File

@ -5,6 +5,7 @@ import logging
from collections import deque from collections import deque
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.enums import RPCMessageType from freqtrade.enums import RPCMessageType
from freqtrade.rpc import RPC, RPCHandler from freqtrade.rpc import RPC, RPCHandler
@ -77,6 +78,8 @@ class RPCManager:
mod.send_msg(msg) mod.send_msg(msg)
except NotImplementedError: except NotImplementedError:
logger.error(f"Message type '{msg['type']}' not implemented by handler {mod.name}.") logger.error(f"Message type '{msg['type']}' not implemented by handler {mod.name}.")
except Exception:
logger.exception('Exception occurred within RPC module %s', mod.name)
def process_msg_queue(self, queue: deque) -> None: def process_msg_queue(self, queue: deque) -> None:
""" """
@ -89,7 +92,7 @@ class RPCManager:
'msg': msg, 'msg': msg,
}) })
def startup_messages(self, config: Dict[str, Any], pairlist, protections) -> None: def startup_messages(self, config: Config, pairlist, protections) -> None:
if config['dry_run']: if config['dry_run']:
self.send_msg({ self.send_msg({
'type': RPCMessageType.WARNING, 'type': RPCMessageType.WARNING,

View File

@ -24,7 +24,7 @@ from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler,
from telegram.utils.helpers import escape_markdown from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN from freqtrade.constants import DUST_PER_COIN, Config
from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.misc import chunks, plural, round_coin_value
@ -88,7 +88,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
class Telegram(RPCHandler): class Telegram(RPCHandler):
""" This class handles all telegram communication """ """ This class handles all telegram communication """
def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: def __init__(self, rpc: RPC, config: Config) -> None:
""" """
Init the Telegram call, and init the super class RPCHandler Init the Telegram call, and init the super class RPCHandler
:param rpc: instance of RPC Helper class :param rpc: instance of RPC Helper class
@ -286,7 +286,7 @@ class Telegram(RPCHandler):
if msg['type'] in [RPCMessageType.ENTRY_FILL]: if msg['type'] in [RPCMessageType.ENTRY_FILL]:
message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n" message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
elif msg['type'] in [RPCMessageType.ENTRY]: elif msg['type'] in [RPCMessageType.ENTRY]:
message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\ message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"\
f"*Current Rate:* `{msg['current_rate']:.8f}`\n" f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
@ -353,8 +353,9 @@ class Telegram(RPCHandler):
f"*Open Rate:* `{msg['open_rate']:.8f}`\n" f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
) )
if msg['type'] == RPCMessageType.EXIT: if msg['type'] == RPCMessageType.EXIT:
message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n" message += f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
f"*Exit Rate:* `{msg['limit']:.8f}`") if msg['order_rate']:
message += f"*Exit Rate:* `{msg['order_rate']:.8f}`"
elif msg['type'] == RPCMessageType.EXIT_FILL: elif msg['type'] == RPCMessageType.EXIT_FILL:
message += f"*Exit Rate:* `{msg['close_rate']:.8f}`" message += f"*Exit Rate:* `{msg['close_rate']:.8f}`"

View File

@ -7,6 +7,7 @@ from typing import Any, Dict
from requests import RequestException, post from requests import RequestException, post
from freqtrade.constants import Config
from freqtrade.enums import RPCMessageType from freqtrade.enums import RPCMessageType
from freqtrade.rpc import RPC, RPCHandler from freqtrade.rpc import RPC, RPCHandler
@ -19,7 +20,7 @@ logger.debug('Included module rpc.webhook ...')
class Webhook(RPCHandler): class Webhook(RPCHandler):
""" This class handles all webhook communication """ """ This class handles all webhook communication """
def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: def __init__(self, rpc: RPC, config: Config) -> None:
""" """
Init the Webhook class, and init the super class RPCHandler Init the Webhook class, and init the super class RPCHandler
:param rpc: instance of RPC Helper class :param rpc: instance of RPC Helper class

View File

@ -6,6 +6,7 @@ import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Tuple, Type, Union from typing import Any, Dict, Iterator, List, Tuple, Type, Union
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, json_load from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
@ -21,7 +22,7 @@ class HyperStrategyMixin:
strategy logic. strategy logic.
""" """
def __init__(self, config: Dict[str, Any], *args, **kwargs): def __init__(self, config: Config, *args, **kwargs):
""" """
Initialize hyperoptable strategy mixin. Initialize hyperoptable strategy mixin.
""" """

View File

@ -10,7 +10,7 @@ from typing import Dict, List, Optional, Tuple, Union
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection,
SignalTagType, SignalType, TradingMode) SignalTagType, SignalType, TradingMode)
@ -118,7 +118,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# Definition of plot_config. See plotting documentation for more details. # Definition of plot_config. See plotting documentation for more details.
plot_config: Dict = {} plot_config: Dict = {}
def __init__(self, config: dict) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
# Dict to determine if analysis is necessary # Dict to determine if analysis is necessary
self._last_candle_seen_per_pair: Dict[str, datetime] = {} self._last_candle_seen_per_pair: Dict[str, datetime] = {}
@ -613,6 +613,22 @@ class IStrategy(ABC, HyperStrategyMixin):
# END - Intended to be overridden by strategy # END - Intended to be overridden by strategy
### ###
def __informative_pairs_freqai(self) -> ListPairsWithTimeframes:
"""
Create informative-pairs needed for FreqAI
"""
if self.config.get('freqai', {}).get('enabled', False):
whitelist_pairs = self.dp.current_whitelist()
candle_type = self.config.get('candle_type_def', CandleType.SPOT)
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
informative_pairs = []
for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
for pair in set(whitelist_pairs + corr_pairs):
informative_pairs.append((pair, tf, candle_type))
return informative_pairs
return []
def gather_informative_pairs(self) -> ListPairsWithTimeframes: def gather_informative_pairs(self) -> ListPairsWithTimeframes:
""" """
Internal method which gathers all informative pairs (user or automatically defined). Internal method which gathers all informative pairs (user or automatically defined).
@ -637,6 +653,7 @@ class IStrategy(ABC, HyperStrategyMixin):
else: else:
for pair in self.dp.current_whitelist(): for pair in self.dp.current_whitelist():
informative_pairs.append((pair, inf_data.timeframe, candle_type)) informative_pairs.append((pair, inf_data.timeframe, candle_type))
informative_pairs.extend(self.__informative_pairs_freqai())
return list(set(informative_pairs)) return list(set(informative_pairs))
def get_strategy_name(self) -> str: def get_strategy_name(self) -> str:

View File

@ -45,20 +45,7 @@ class FreqaiExampleStrategy(IStrategy):
std_dev_multiplier_buy = CategoricalParameter( std_dev_multiplier_buy = CategoricalParameter(
[0.75, 1, 1.25, 1.5, 1.75], default=1.25, space="buy", optimize=True) [0.75, 1, 1.25, 1.5, 1.75], default=1.25, space="buy", optimize=True)
std_dev_multiplier_sell = CategoricalParameter( std_dev_multiplier_sell = CategoricalParameter(
[0.1, 0.25, 0.4], space="sell", default=0.2, optimize=True) [0.75, 1, 1.25, 1.5, 1.75], space="sell", default=1.25, optimize=True)
def informative_pairs(self):
whitelist_pairs = self.dp.current_whitelist()
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
informative_pairs = []
for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
for pair in whitelist_pairs:
informative_pairs.append((pair, tf))
for pair in corr_pairs:
if pair in whitelist_pairs:
continue # avoid duplication
informative_pairs.append((pair, tf))
return informative_pairs
def populate_any_indicators( def populate_any_indicators(
self, pair, df, tf, informative=None, set_generalized_indicators=False self, pair, df, tf, informative=None, set_generalized_indicators=False
@ -183,25 +170,31 @@ class FreqaiExampleStrategy(IStrategy):
dataframe = self.freqai.start(dataframe, metadata, self) dataframe = self.freqai.start(dataframe, metadata, self)
for val in self.std_dev_multiplier_buy.range: for val in self.std_dev_multiplier_buy.range:
dataframe[f'target_roi_{val}'] = dataframe["&-s_close_mean"] + \ dataframe[f'target_roi_{val}'] = (
dataframe["&-s_close_std"] * val dataframe["&-s_close_mean"] + dataframe["&-s_close_std"] * val
)
for val in self.std_dev_multiplier_sell.range: for val in self.std_dev_multiplier_sell.range:
dataframe[f'sell_roi_{val}'] = dataframe["&-s_close_mean"] - \ dataframe[f'sell_roi_{val}'] = (
dataframe["&-s_close_std"] * val dataframe["&-s_close_mean"] - dataframe["&-s_close_std"] * val
)
return dataframe return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [df["do_predict"] == 1, df["&-s_close"] enter_long_conditions = [
> df[f"target_roi_{self.std_dev_multiplier_buy.value}"]] df["do_predict"] == 1,
df["&-s_close"] > df[f"target_roi_{self.std_dev_multiplier_buy.value}"],
]
if enter_long_conditions: if enter_long_conditions:
df.loc[ df.loc[
reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"] reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"]
] = (1, "long") ] = (1, "long")
enter_short_conditions = [df["do_predict"] == 1, df["&-s_close"] enter_short_conditions = [
< df[f"sell_roi_{self.std_dev_multiplier_sell.value}"]] df["do_predict"] == 1,
df["&-s_close"] < df[f"sell_roi_{self.std_dev_multiplier_sell.value}"],
]
if enter_short_conditions: if enter_short_conditions:
df.loc[ df.loc[
@ -211,13 +204,17 @@ class FreqaiExampleStrategy(IStrategy):
return df return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [df["do_predict"] == 1, df["&-s_close"] < exit_long_conditions = [
df[f"sell_roi_{self.std_dev_multiplier_sell.value}"] * 0.25] df["do_predict"] == 1,
df["&-s_close"] < df[f"sell_roi_{self.std_dev_multiplier_sell.value}"] * 0.25,
]
if exit_long_conditions: if exit_long_conditions:
df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1 df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1
exit_short_conditions = [df["do_predict"] == 1, df["&-s_close"] > exit_short_conditions = [
df[f"target_roi_{self.std_dev_multiplier_buy.value}"] * 0.25] df["do_predict"] == 1,
df["&-s_close"] > df[f"target_roi_{self.std_dev_multiplier_buy.value}"] * 0.25,
]
if exit_short_conditions: if exit_short_conditions:
df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1 df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1

View File

@ -95,20 +95,6 @@ class FreqaiExampleHybridStrategy(IStrategy):
short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True)
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
# FreqAI required function, leave as is or add additional informatives to existing structure.
def informative_pairs(self):
whitelist_pairs = self.dp.current_whitelist()
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
informative_pairs = []
for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
for pair in whitelist_pairs:
informative_pairs.append((pair, tf))
for pair in corr_pairs:
if pair in whitelist_pairs:
continue # avoid duplication
informative_pairs.append((pair, tf))
return informative_pairs
# FreqAI required function, user can add or remove indicators, but general structure # FreqAI required function, user can add or remove indicators, but general structure
# must stay the same. # must stay the same.
def populate_any_indicators( def populate_any_indicators(

View File

@ -1,21 +1,21 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401 # flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs --- # --- Do not remove these libs ---
import numpy as np # noqa import numpy as np
import pandas as pd # noqa import pandas as pd
from pandas import DataFrame # noqa from pandas import DataFrame
from datetime import datetime # noqa from datetime import datetime
from typing import Optional, Union # noqa from typing import Optional, Union
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
IStrategy, IntParameter) IntParameter, IStrategy, merge_informative_pair)
# -------------------------------- # --------------------------------
# Add your lib to import here # Add your lib to import here
import talib.abstract as ta import talib.abstract as ta
import pandas_ta as pta import pandas_ta as pta
import freqtrade.vendor.qtpylib.indicators as qtpylib from technical import qtpylib
class {{ strategy }}(IStrategy): class {{ strategy }}(IStrategy):

View File

@ -4,6 +4,7 @@ from typing import Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -36,7 +37,7 @@ class SampleHyperOptLoss(IHyperOptLoss):
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame], config: Config, processed: Dict[str, DataFrame],
*args, **kwargs) -> float: *args, **kwargs) -> float:
""" """
Objective function, returns smaller number for better results Objective function, returns smaller number for better results

View File

@ -7,7 +7,7 @@ from typing import Dict, NamedTuple, Optional
import arrow import arrow
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.enums import RunMode, TradingMode from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import DependencyException from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
@ -35,7 +35,7 @@ class PositionWallet(NamedTuple):
class Wallets: class Wallets:
def __init__(self, config: dict, exchange: Exchange, log: bool = True) -> None: def __init__(self, config: Config, exchange: Exchange, log: bool = True) -> None:
self._config = config self._config = config
self._log = log self._log = log
self._exchange = exchange self._exchange = exchange

View File

@ -9,8 +9,9 @@ from typing import Any, Callable, Dict, Optional
import sdnotify import sdnotify
from freqtrade import __version__, constants from freqtrade import __version__
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.constants import PROCESS_THROTTLE_SECS, RETRY_TIMEOUT, Config
from freqtrade.enums import State from freqtrade.enums import State
from freqtrade.exceptions import OperationalException, TemporaryError from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
@ -24,7 +25,7 @@ class Worker:
Freqtradebot worker class Freqtradebot worker class
""" """
def __init__(self, args: Dict[str, Any], config: Dict[str, Any] = None) -> None: def __init__(self, args: Dict[str, Any], config: Config = None) -> None:
""" """
Init all variables and objects the bot needs to work Init all variables and objects the bot needs to work
""" """
@ -53,7 +54,7 @@ class Worker:
internals_config = self._config.get('internals', {}) internals_config = self._config.get('internals', {})
self._throttle_secs = internals_config.get('process_throttle_secs', self._throttle_secs = internals_config.get('process_throttle_secs',
constants.PROCESS_THROTTLE_SECS) PROCESS_THROTTLE_SECS)
self._heartbeat_interval = internals_config.get('heartbeat_interval', 60) self._heartbeat_interval = internals_config.get('heartbeat_interval', 60)
self._sd_notify = sdnotify.SystemdNotifier() if \ self._sd_notify = sdnotify.SystemdNotifier() if \
@ -151,8 +152,8 @@ class Worker:
try: try:
self.freqtrade.process() self.freqtrade.process()
except TemporaryError as error: except TemporaryError as error:
logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") logger.warning(f"Error: {error}, retrying in {RETRY_TIMEOUT} seconds...")
time.sleep(constants.RETRY_TIMEOUT) time.sleep(RETRY_TIMEOUT)
except OperationalException: except OperationalException:
tb = traceback.format_exc() tb = traceback.format_exc()
hint = 'Issue `/start` if you think it is safe to restart.' hint = 'Issue `/start` if you think it is safe to restart.'

Some files were not shown because too many files have changed in this diff Show More