Merge branch 'develop' into pr/theluxaz/5710
This commit is contained in:
commit
78724e304e
@ -52,6 +52,71 @@ freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user
|
|||||||
|
|
||||||
For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md).
|
For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md).
|
||||||
|
|
||||||
|
### Multiple instances using docker
|
||||||
|
|
||||||
|
To run multiple instances of freqtrade using docker you will need to edit the docker-compose.yml file and add all the instances you want as separate services. Remember, you can separate your configuration into multiple files, so it's a good idea to think about making them modular, then if you need to edit something common to all bots, you can do that in a single config file.
|
||||||
|
``` yml
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
freqtrade1:
|
||||||
|
image: freqtradeorg/freqtrade:stable
|
||||||
|
# image: freqtradeorg/freqtrade:develop
|
||||||
|
# Use plotting image
|
||||||
|
# image: freqtradeorg/freqtrade:develop_plot
|
||||||
|
# Build step - only needed when additional dependencies are needed
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: "./docker/Dockerfile.custom"
|
||||||
|
restart: always
|
||||||
|
container_name: freqtrade1
|
||||||
|
volumes:
|
||||||
|
- "./user_data:/freqtrade/user_data"
|
||||||
|
# Expose api on port 8080 (localhost only)
|
||||||
|
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
||||||
|
# before enabling this.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8080:8080"
|
||||||
|
# Default command used when running `docker compose up`
|
||||||
|
command: >
|
||||||
|
trade
|
||||||
|
--logfile /freqtrade/user_data/logs/freqtrade1.log
|
||||||
|
--db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade1.sqlite
|
||||||
|
--config /freqtrade/user_data/config.json
|
||||||
|
--config /freqtrade/user_data/config.freqtrade1.json
|
||||||
|
--strategy SampleStrategy
|
||||||
|
|
||||||
|
freqtrade2:
|
||||||
|
image: freqtradeorg/freqtrade:stable
|
||||||
|
# image: freqtradeorg/freqtrade:develop
|
||||||
|
# Use plotting image
|
||||||
|
# image: freqtradeorg/freqtrade:develop_plot
|
||||||
|
# Build step - only needed when additional dependencies are needed
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: "./docker/Dockerfile.custom"
|
||||||
|
restart: always
|
||||||
|
container_name: freqtrade2
|
||||||
|
volumes:
|
||||||
|
- "./user_data:/freqtrade/user_data"
|
||||||
|
# Expose api on port 8080 (localhost only)
|
||||||
|
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
||||||
|
# before enabling this.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8081:8080"
|
||||||
|
# Default command used when running `docker compose up`
|
||||||
|
command: >
|
||||||
|
trade
|
||||||
|
--logfile /freqtrade/user_data/logs/freqtrade2.log
|
||||||
|
--db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade2.sqlite
|
||||||
|
--config /freqtrade/user_data/config.json
|
||||||
|
--config /freqtrade/user_data/config.freqtrade2.json
|
||||||
|
--strategy SampleStrategy
|
||||||
|
|
||||||
|
```
|
||||||
|
You can use whatever naming convention you want, freqtrade1 and 2 are arbitrary. Note, that you will need to use different database files, port mappings and telegram configurations for each instance, as mentioned above.
|
||||||
|
|
||||||
|
|
||||||
## Configure the bot running as a systemd service
|
## Configure the bot running as a systemd service
|
||||||
|
|
||||||
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
||||||
|
@ -21,6 +21,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
[--export {none,trades}] [--export-filename PATH]
|
[--export {none,trades}] [--export-filename PATH]
|
||||||
|
[--breakdown {day,week,month} [{day,week,month} ...]]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -30,7 +31,7 @@ optional arguments:
|
|||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
--data-format-ohlcv {json,jsongz,hdf5}
|
--data-format-ohlcv {json,jsongz,hdf5}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `None`).
|
(default: `json`).
|
||||||
--max-open-trades INT
|
--max-open-trades INT
|
||||||
Override the value of the `max_open_trades`
|
Override the value of the `max_open_trades`
|
||||||
configuration setting.
|
configuration setting.
|
||||||
@ -65,8 +66,7 @@ optional arguments:
|
|||||||
set either in config or via command line. When using
|
set either in config or via command line. When using
|
||||||
this together with `--export trades`, the strategy-
|
this together with `--export trades`, the strategy-
|
||||||
name is injected into the filename (so `backtest-
|
name is injected into the filename (so `backtest-
|
||||||
data.json` becomes `backtest-data-
|
data.json` becomes `backtest-data-SampleStrategy.json`
|
||||||
SampleStrategy.json`
|
|
||||||
--export {none,trades}
|
--export {none,trades}
|
||||||
Export backtest results (default: trades).
|
Export backtest results (default: trades).
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
@ -74,6 +74,8 @@ optional arguments:
|
|||||||
Requires `--export` to be set as well. Example:
|
Requires `--export` to be set as well. Example:
|
||||||
`--export-filename=user_data/backtest_results/backtest
|
`--export-filename=user_data/backtest_results/backtest
|
||||||
_today.json`
|
_today.json`
|
||||||
|
--breakdown {day,week,month} [{day,week,month} ...]
|
||||||
|
Show backtesting breakdown per [day, week, month].
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
@ -429,6 +431,31 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
||||||
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
|
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
|
||||||
|
|
||||||
|
### Daily / Weekly / Monthly breakdown
|
||||||
|
|
||||||
|
You can get an overview over daily / weekly or monthly results by using the `--breakdown <>` switch.
|
||||||
|
|
||||||
|
To visualize daily and weekly breakdowns, you can use the following:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day month
|
||||||
|
```
|
||||||
|
|
||||||
|
``` output
|
||||||
|
======================== DAY BREAKDOWN =========================
|
||||||
|
| Day | Tot Profit USDT | Wins | Draws | Losses |
|
||||||
|
|------------+-------------------+--------+---------+----------|
|
||||||
|
| 03/07/2021 | 200.0 | 2 | 0 | 0 |
|
||||||
|
| 04/07/2021 | -50.31 | 0 | 0 | 2 |
|
||||||
|
| 05/07/2021 | 220.611 | 3 | 2 | 0 |
|
||||||
|
| 06/07/2021 | 150.974 | 3 | 0 | 2 |
|
||||||
|
| 07/07/2021 | -70.193 | 1 | 0 | 2 |
|
||||||
|
| 08/07/2021 | 212.413 | 2 | 0 | 3 |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will show a table containing the realized absolute Profit (in stake currency) for the given timeperiod, as well as wins, draws and losses that materialized (closed) on this day.
|
||||||
|
|
||||||
### Further backtest-result analysis
|
### Further backtest-result analysis
|
||||||
|
|
||||||
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
||||||
|
@ -52,6 +52,8 @@ To skip pair validation against active markets, set `"allow_inactive": true` wit
|
|||||||
This can be useful for backtesting expired pairs (like quarterly spot-markets).
|
This can be useful for backtesting expired pairs (like quarterly spot-markets).
|
||||||
This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration.
|
This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration.
|
||||||
|
|
||||||
|
When used in a "follow-up" position (e.g. after VolumePairlist), all pairs in `'pair_whitelist'` will be added to the end of the pairlist.
|
||||||
|
|
||||||
#### Volume Pair List
|
#### Volume Pair List
|
||||||
|
|
||||||
`VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`).
|
`VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`).
|
||||||
|
@ -281,7 +281,7 @@ bitmax True missing opt: fetchMyTrades
|
|||||||
bitmex False Various reasons.
|
bitmex False Various reasons.
|
||||||
bitpanda True
|
bitpanda True
|
||||||
bitso False missing: fetchOHLCV
|
bitso False missing: fetchOHLCV
|
||||||
bitstamp False Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983
|
bitstamp True missing opt: fetchTickers
|
||||||
bitstamp1 False missing: fetchOrder, fetchOHLCV
|
bitstamp1 False missing: fetchOrder, fetchOHLCV
|
||||||
bittrex True
|
bittrex True
|
||||||
bitvavo True
|
bitvavo True
|
||||||
@ -667,6 +667,7 @@ usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--profitable] [-n INT] [--print-json]
|
[--profitable] [-n INT] [--print-json]
|
||||||
[--hyperopt-filename FILENAME] [--no-header]
|
[--hyperopt-filename FILENAME] [--no-header]
|
||||||
[--disable-param-export]
|
[--disable-param-export]
|
||||||
|
[--breakdown {day,week,month} [{day,week,month} ...]]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -680,6 +681,8 @@ optional arguments:
|
|||||||
--no-header Do not print epoch details header.
|
--no-header Do not print epoch details header.
|
||||||
--disable-param-export
|
--disable-param-export
|
||||||
Disable automatic hyperopt parameter export.
|
Disable automatic hyperopt parameter export.
|
||||||
|
--breakdown {day,week,month} [{day,week,month} ...]
|
||||||
|
Show backtesting breakdown per [day, week, month].
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
@ -23,7 +23,8 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
|||||||
|
|
||||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
||||||
"enable_protections", "dry_run_wallet", "timeframe_detail",
|
"enable_protections", "dry_run_wallet", "timeframe_detail",
|
||||||
"strategy_list", "export", "exportfilename"]
|
"strategy_list", "export", "exportfilename",
|
||||||
|
"backtest_breakdown"]
|
||||||
|
|
||||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||||
"position_stacking", "use_max_market_positions",
|
"position_stacking", "use_max_market_positions",
|
||||||
@ -89,7 +90,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
|
|||||||
|
|
||||||
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
||||||
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header",
|
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header",
|
||||||
"disableparamexport"]
|
"disableparamexport", "backtest_breakdown"]
|
||||||
|
|
||||||
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
|
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
|
||||||
"list-markets", "list-pairs", "list-strategies", "list-data",
|
"list-markets", "list-pairs", "list-strategies", "list-data",
|
||||||
|
@ -193,6 +193,12 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
type=float,
|
type=float,
|
||||||
metavar='FLOAT',
|
metavar='FLOAT',
|
||||||
),
|
),
|
||||||
|
"backtest_breakdown": Arg(
|
||||||
|
'--breakdown',
|
||||||
|
help='Show backtesting breakdown per [day, week, month].',
|
||||||
|
nargs='+',
|
||||||
|
choices=constants.BACKTEST_BREAKDOWNS
|
||||||
|
),
|
||||||
# Edge
|
# Edge
|
||||||
"stoploss_range": Arg(
|
"stoploss_range": Arg(
|
||||||
'--stoplosses',
|
'--stoplosses',
|
||||||
|
@ -96,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
|||||||
if 'strategy_name' in metrics:
|
if 'strategy_name' in metrics:
|
||||||
strategy_name = metrics['strategy_name']
|
strategy_name = metrics['strategy_name']
|
||||||
show_backtest_result(strategy_name, metrics,
|
show_backtest_result(strategy_name, metrics,
|
||||||
metrics['stake_currency'])
|
metrics['stake_currency'], config.get('backtest_breakdown', []))
|
||||||
|
|
||||||
HyperoptTools.try_export_params(config, strategy_name, val)
|
HyperoptTools.try_export_params(config, strategy_name, val)
|
||||||
|
|
||||||
|
@ -269,8 +269,12 @@ class Configuration:
|
|||||||
self._args_to_config(config, argname='export',
|
self._args_to_config(config, argname='export',
|
||||||
logstring='Parameter --export detected: {} ...')
|
logstring='Parameter --export detected: {} ...')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='backtest_breakdown',
|
||||||
|
logstring='Parameter --breakdown detected ...')
|
||||||
|
|
||||||
self._args_to_config(config, argname='disableparamexport',
|
self._args_to_config(config, argname='disableparamexport',
|
||||||
logstring='Parameter --disableparamexport detected: {} ...')
|
logstring='Parameter --disableparamexport detected: {} ...')
|
||||||
|
|
||||||
# Edge section:
|
# Edge section:
|
||||||
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
||||||
txt_range = eval(self.args["stoploss_range"])
|
txt_range = eval(self.args["stoploss_range"])
|
||||||
|
@ -32,6 +32,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
|||||||
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
|
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
|
||||||
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
||||||
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
|
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
|
||||||
|
BACKTEST_BREAKDOWNS = ['day', 'week', 'month']
|
||||||
DRY_RUN_WALLET = 1000
|
DRY_RUN_WALLET = 1000
|
||||||
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
|
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||||
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
||||||
@ -146,6 +147,10 @@ CONF_SCHEMA = {
|
|||||||
'sell_profit_offset': {'type': 'number'},
|
'sell_profit_offset': {'type': 'number'},
|
||||||
'ignore_roi_if_buy_signal': {'type': 'boolean'},
|
'ignore_roi_if_buy_signal': {'type': 'boolean'},
|
||||||
'ignore_buying_expired_candle_after': {'type': 'number'},
|
'ignore_buying_expired_candle_after': {'type': 'number'},
|
||||||
|
'backtest_breakdown': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS}
|
||||||
|
},
|
||||||
'bot_name': {'type': 'string'},
|
'bot_name': {'type': 'string'},
|
||||||
'unfilledtimeout': {
|
'unfilledtimeout': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
|
@ -16,8 +16,6 @@ API_FETCH_ORDER_RETRY_COUNT = 5
|
|||||||
|
|
||||||
BAD_EXCHANGES = {
|
BAD_EXCHANGES = {
|
||||||
"bitmex": "Various reasons.",
|
"bitmex": "Various reasons.",
|
||||||
"bitstamp": "Does not provide history. "
|
|
||||||
"Details in https://github.com/freqtrade/freqtrade/issues/1983",
|
|
||||||
"phemex": "Does not provide history. ",
|
"phemex": "Does not provide history. ",
|
||||||
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
|
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ progressbar.streams.wrap_stdout()
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
INITIAL_POINTS = 5
|
INITIAL_POINTS = 30
|
||||||
|
|
||||||
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
|
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
|
||||||
# in the skopt model queue, to optimize memory consumption
|
# in the skopt model queue, to optimize memory consumption
|
||||||
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
from numpy import int64
|
from numpy import int64
|
||||||
from pandas import DataFrame
|
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
|
||||||
@ -263,7 +263,6 @@ def generate_strategy_comparison(all_results: Dict) -> List[Dict]:
|
|||||||
|
|
||||||
|
|
||||||
def generate_edge_table(results: dict) -> str:
|
def generate_edge_table(results: dict) -> str:
|
||||||
|
|
||||||
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd')
|
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd')
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio',
|
headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio',
|
||||||
@ -288,6 +287,41 @@ def generate_edge_table(results: dict) -> str:
|
|||||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
|
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def _get_resample_from_period(period: str) -> str:
|
||||||
|
if period == 'day':
|
||||||
|
return '1d'
|
||||||
|
if period == 'week':
|
||||||
|
return '1w'
|
||||||
|
if period == 'month':
|
||||||
|
return '1M'
|
||||||
|
raise ValueError(f"Period {period} is not supported.")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dict[str, Any]]:
|
||||||
|
results = DataFrame.from_records(trade_list)
|
||||||
|
if len(results) == 0:
|
||||||
|
return []
|
||||||
|
results['close_date'] = to_datetime(results['close_date'], utc=True)
|
||||||
|
resample_period = _get_resample_from_period(period)
|
||||||
|
resampled = results.resample(resample_period, on='close_date')
|
||||||
|
stats = []
|
||||||
|
for name, day in resampled:
|
||||||
|
profit_abs = day['profit_abs'].sum().round(10)
|
||||||
|
wins = sum(day['profit_abs'] > 0)
|
||||||
|
draws = sum(day['profit_abs'] == 0)
|
||||||
|
loses = sum(day['profit_abs'] < 0)
|
||||||
|
stats.append(
|
||||||
|
{
|
||||||
|
'date': name.strftime('%d/%m/%Y'),
|
||||||
|
'profit_abs': profit_abs,
|
||||||
|
'wins': wins,
|
||||||
|
'draws': draws,
|
||||||
|
'loses': loses
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
|
def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
|
||||||
""" Generate overall trade statistics """
|
""" Generate overall trade statistics """
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
@ -407,7 +441,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
results['open_timestamp'] = results['open_date'].view(int64) // 1e6
|
results['open_timestamp'] = results['open_date'].view(int64) // 1e6
|
||||||
results['close_timestamp'] = results['close_date'].view(int64) // 1e6
|
results['close_timestamp'] = results['close_date'].view(int64) // 1e6
|
||||||
|
|
||||||
backtest_days = (max_date - min_date).days
|
backtest_days = (max_date - min_date).days or 1
|
||||||
strat_stats = {
|
strat_stats = {
|
||||||
'trades': results.to_dict(orient='records'),
|
'trades': results.to_dict(orient='records'),
|
||||||
'locks': [lock.to_json() for lock in content['locks']],
|
'locks': [lock.to_json() for lock in content['locks']],
|
||||||
@ -417,6 +451,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
'results_per_buy_tag': buy_tag_results,
|
'results_per_buy_tag': buy_tag_results,
|
||||||
'sell_reason_summary': sell_reason_stats,
|
'sell_reason_summary': sell_reason_stats,
|
||||||
'left_open_trades': left_open_results,
|
'left_open_trades': left_open_results,
|
||||||
|
# 'days_breakdown_stats': days_breakdown_stats,
|
||||||
|
|
||||||
'total_trades': len(results),
|
'total_trades': len(results),
|
||||||
'total_volume': float(results['stake_amount'].sum()),
|
'total_volume': float(results['stake_amount'].sum()),
|
||||||
'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0,
|
'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0,
|
||||||
@ -433,7 +469,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
'backtest_run_start_ts': content['backtest_start_time'],
|
'backtest_run_start_ts': content['backtest_start_time'],
|
||||||
'backtest_run_end_ts': content['backtest_end_time'],
|
'backtest_run_end_ts': content['backtest_end_time'],
|
||||||
|
|
||||||
'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0,
|
'trades_per_day': round(len(results) / backtest_days, 2),
|
||||||
'market_change': market_change,
|
'market_change': market_change,
|
||||||
'pairlist': list(btdata.keys()),
|
'pairlist': list(btdata.keys()),
|
||||||
'stake_amount': config['stake_amount'],
|
'stake_amount': config['stake_amount'],
|
||||||
@ -616,6 +652,28 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
|
|||||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
||||||
|
|
||||||
|
|
||||||
|
def text_table_periodic_breakdown(days_breakdown_stats: List[Dict[str, Any]],
|
||||||
|
stake_currency: str, period: str) -> str:
|
||||||
|
"""
|
||||||
|
Generate small table with Backtest results by days
|
||||||
|
:param days_breakdown_stats: Days breakdown metrics
|
||||||
|
:param stake_currency: Stakecurrency used
|
||||||
|
:return: pretty printed table with tabulate as string
|
||||||
|
"""
|
||||||
|
headers = [
|
||||||
|
period.capitalize(),
|
||||||
|
f'Tot Profit {stake_currency}',
|
||||||
|
'Wins',
|
||||||
|
'Draws',
|
||||||
|
'Losses',
|
||||||
|
]
|
||||||
|
output = [[
|
||||||
|
d['date'], round_coin_value(d['profit_abs'], stake_currency, False),
|
||||||
|
d['wins'], d['draws'], d['loses'],
|
||||||
|
] for d in days_breakdown_stats]
|
||||||
|
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
|
||||||
|
|
||||||
|
|
||||||
def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
||||||
"""
|
"""
|
||||||
Generate summary table per strategy
|
Generate summary table per strategy
|
||||||
@ -667,7 +725,10 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
strat_results['stake_currency'])),
|
strat_results['stake_currency'])),
|
||||||
('Absolute profit ', round_coin_value(strat_results['profit_total_abs'],
|
('Absolute profit ', round_coin_value(strat_results['profit_total_abs'],
|
||||||
strat_results['stake_currency'])),
|
strat_results['stake_currency'])),
|
||||||
('Total profit %', f"{round(strat_results['profit_total'] * 100, 2):}%"),
|
('Total profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"),
|
||||||
|
('Trades per day', strat_results['trades_per_day']),
|
||||||
|
('Avg. daily profit %',
|
||||||
|
f"{round(strat_results['profit_total'] / strat_results['backtest_days'] * 100, 2)}%"),
|
||||||
('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'],
|
('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'],
|
||||||
strat_results['stake_currency'])),
|
strat_results['stake_currency'])),
|
||||||
('Total trade volume', round_coin_value(strat_results['total_volume'],
|
('Total trade volume', round_coin_value(strat_results['total_volume'],
|
||||||
@ -724,7 +785,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str):
|
def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str,
|
||||||
|
backtest_breakdown=[]):
|
||||||
"""
|
"""
|
||||||
Print results for one strategy
|
Print results for one strategy
|
||||||
"""
|
"""
|
||||||
@ -756,6 +818,15 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
for period in backtest_breakdown:
|
||||||
|
days_breakdown_stats = generate_periodic_breakdown_stats(
|
||||||
|
trade_list=results['trades'], period=period)
|
||||||
|
table = text_table_periodic_breakdown(days_breakdown_stats=days_breakdown_stats,
|
||||||
|
stake_currency=stake_currency, period=period)
|
||||||
|
if isinstance(table, str) and len(table) > 0:
|
||||||
|
print(f' {period.upper()} BREAKDOWN '.center(len(table.splitlines()[0]), '='))
|
||||||
|
print(table)
|
||||||
|
|
||||||
table = text_table_add_metrics(results)
|
table = text_table_add_metrics(results)
|
||||||
if isinstance(table, str) and len(table) > 0:
|
if isinstance(table, str) and len(table) > 0:
|
||||||
print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
|
print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
|
||||||
@ -771,7 +842,9 @@ def show_backtest_results(config: Dict, 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():
|
||||||
show_backtest_result(strategy, results, stake_currency)
|
show_backtest_result(
|
||||||
|
strategy, results, stake_currency,
|
||||||
|
config.get('backtest_breakdown', []))
|
||||||
|
|
||||||
if len(backtest_stats['strategy']) > 1:
|
if len(backtest_stats['strategy']) > 1:
|
||||||
# Print Strategy summary table
|
# Print Strategy summary table
|
||||||
|
@ -4,9 +4,9 @@ Static Pair List provider
|
|||||||
Provides pair white list as it configured in config
|
Provides pair white list as it configured in config
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
from freqtrade.plugins.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
@ -20,10 +20,6 @@ class StaticPairList(IPairList):
|
|||||||
pairlist_pos: int) -> None:
|
pairlist_pos: int) -> None:
|
||||||
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
||||||
|
|
||||||
if self._pairlist_pos != 0:
|
|
||||||
raise OperationalException(f"{self.name} can only be used in the first position "
|
|
||||||
"in the list of Pairlist Handlers.")
|
|
||||||
|
|
||||||
self._allow_inactive = self._pairlistconfig.get('allow_inactive', False)
|
self._allow_inactive = self._pairlistconfig.get('allow_inactive', False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -64,4 +60,8 @@ class StaticPairList(IPairList):
|
|||||||
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
||||||
:return: new whitelist
|
:return: new whitelist
|
||||||
"""
|
"""
|
||||||
return pairlist
|
pairlist_ = deepcopy(pairlist)
|
||||||
|
for pair in self._config['exchange']['pair_whitelist']:
|
||||||
|
if pair not in pairlist_:
|
||||||
|
pairlist_.append(pair)
|
||||||
|
return pairlist_
|
||||||
|
@ -1104,6 +1104,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'--timerange', '1510694220-1510700340',
|
'--timerange', '1510694220-1510700340',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
|
'--breakdown', 'day',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'StrategyTestV2',
|
'StrategyTestV2',
|
||||||
'TestStrategyLegacyV1',
|
'TestStrategyLegacyV1',
|
||||||
@ -1132,6 +1133,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert 'BACKTESTING REPORT' in captured.out
|
assert 'BACKTESTING REPORT' in captured.out
|
||||||
assert 'SELL REASON STATS' in captured.out
|
assert 'SELL REASON STATS' in captured.out
|
||||||
|
assert 'DAY BREAKDOWN' in captured.out
|
||||||
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
||||||
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
|
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
|
||||||
assert 'STRATEGY SUMMARY' in captured.out
|
assert 'STRATEGY SUMMARY' in captured.out
|
||||||
|
@ -13,8 +13,10 @@ from freqtrade.data import history
|
|||||||
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
|
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
|
||||||
from freqtrade.edge import PairInfo
|
from freqtrade.edge import PairInfo
|
||||||
from freqtrade.enums import SellType
|
from freqtrade.enums import SellType
|
||||||
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
|
from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats,
|
||||||
generate_edge_table, generate_pair_metrics,
|
generate_daily_stats, generate_edge_table,
|
||||||
|
generate_pair_metrics,
|
||||||
|
generate_periodic_breakdown_stats,
|
||||||
generate_sell_reason_stats,
|
generate_sell_reason_stats,
|
||||||
generate_strategy_comparison,
|
generate_strategy_comparison,
|
||||||
generate_trading_stats, store_backtest_stats,
|
generate_trading_stats, store_backtest_stats,
|
||||||
@ -377,3 +379,31 @@ def test_generate_edge_table():
|
|||||||
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
||||||
assert generate_edge_table(results).count(
|
assert generate_edge_table(results).count(
|
||||||
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_periodic_breakdown_stats(testdatadir):
|
||||||
|
filename = testdatadir / "backtest-result_new.json"
|
||||||
|
bt_data = load_backtest_data(filename).to_dict(orient='records')
|
||||||
|
|
||||||
|
res = generate_periodic_breakdown_stats(bt_data, 'day')
|
||||||
|
assert isinstance(res, list)
|
||||||
|
assert len(res) == 21
|
||||||
|
day = res[0]
|
||||||
|
assert 'date' in day
|
||||||
|
assert 'draws' in day
|
||||||
|
assert 'loses' in day
|
||||||
|
assert 'wins' in day
|
||||||
|
assert 'profit_abs' in day
|
||||||
|
|
||||||
|
# Select empty dataframe!
|
||||||
|
res = generate_periodic_breakdown_stats([], 'day')
|
||||||
|
assert res == []
|
||||||
|
|
||||||
|
|
||||||
|
def test__get_resample_from_period():
|
||||||
|
|
||||||
|
assert _get_resample_from_period('day') == '1d'
|
||||||
|
assert _get_resample_from_period('week') == '1w'
|
||||||
|
assert _get_resample_from_period('month') == '1M'
|
||||||
|
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
|
||||||
|
_get_resample_from_period('noooo')
|
||||||
|
@ -415,10 +415,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
|||||||
# SpreadFilter only
|
# SpreadFilter only
|
||||||
([{"method": "SpreadFilter", "max_spread_ratio": 0.005}],
|
([{"method": "SpreadFilter", "max_spread_ratio": 0.005}],
|
||||||
"BTC", 'filter_at_the_beginning'), # OperationalException expected
|
"BTC", 'filter_at_the_beginning'), # OperationalException expected
|
||||||
# Static Pairlist after VolumePairList, on a non-first position
|
# Static Pairlist after VolumePairList, on a non-first position (appends pairs)
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
([{"method": "VolumePairList", "number_assets": 2, "sort_key": "quoteVolume"},
|
||||||
{"method": "StaticPairList"}],
|
{"method": "StaticPairList"}],
|
||||||
"BTC", 'static_in_the_middle'),
|
"BTC", ['ETH/BTC', 'TKN/BTC', 'TRST/BTC', 'SWT/BTC', 'BCC/BTC', 'HOT/BTC']),
|
||||||
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
|
||||||
{"method": "PriceFilter", "low_price_ratio": 0.02}],
|
{"method": "PriceFilter", "low_price_ratio": 0.02}],
|
||||||
"USDT", ['ETH/USDT', 'NANO/USDT']),
|
"USDT", ['ETH/USDT', 'NANO/USDT']),
|
||||||
@ -469,13 +469,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
|||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||||
|
|
||||||
if whitelist_result == 'static_in_the_middle':
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"StaticPairList can only be used in the first position "
|
|
||||||
r"in the list of Pairlist Handlers."):
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
|
||||||
return
|
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||||
get_tickers=tickers,
|
get_tickers=tickers,
|
||||||
|
Loading…
Reference in New Issue
Block a user