commit
de57da3249
@ -211,6 +211,31 @@ optional arguments:
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Edge commands
|
||||||
|
|
||||||
|
To know your trade expectacny and winrate against historical data, you can use Edge.
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: main.py edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r]
|
||||||
|
[--stoplosses STOPLOSS_RANGE]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
|
||||||
|
specify ticker interval (1m, 5m, 30m, 1h, 1d)
|
||||||
|
--timerange TIMERANGE
|
||||||
|
specify what timerange of data to use.
|
||||||
|
-r, --refresh-pairs-cached
|
||||||
|
refresh the pairs files in tests/testdata with the
|
||||||
|
latest data from the exchange. Use it if you want to
|
||||||
|
run your edge with up-to-date data.
|
||||||
|
--stoplosses STOPLOSS_RANGE
|
||||||
|
defines a range of stoploss against which edge will
|
||||||
|
assess the strategythe format is "min,max,step"
|
||||||
|
(without any space).example:
|
||||||
|
--stoplosses=-0.01,-0.1,-0.001
|
||||||
|
```
|
||||||
|
|
||||||
## A parameter missing in the configuration?
|
## A parameter missing in the configuration?
|
||||||
|
|
||||||
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
|
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
|
||||||
|
56
docs/edge.md
56
docs/edge.md
@ -3,12 +3,14 @@
|
|||||||
This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss.
|
This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss.
|
||||||
|
|
||||||
**NOTICE:** Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist.
|
**NOTICE:** Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist.
|
||||||
|
**NOTICE2:** Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [How does it work?](#how-does-it-work?)
|
- [How does it work?](#how-does-it-work?)
|
||||||
- [Configurations](#configurations)
|
- [Configurations](#configurations)
|
||||||
|
- [Running Edge independently](#running-edge-independently)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.<br/><br/>
|
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.<br/><br/>
|
||||||
@ -149,3 +151,57 @@ Edge will filter out trades with long duration. If a trade is profitable after 1
|
|||||||
#### remove_pumps
|
#### remove_pumps
|
||||||
Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br/>
|
Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br/>
|
||||||
(default to false)
|
(default to false)
|
||||||
|
|
||||||
|
|
||||||
|
## Running Edge independently
|
||||||
|
You can run Edge independently in order to see in details the result. Here is an example:
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py edge
|
||||||
|
```
|
||||||
|
|
||||||
|
An example of its output:
|
||||||
|
|
||||||
|
| pair | stoploss | win rate | risk reward ratio | required risk reward | expectancy | total number of trades | average duration (min) |
|
||||||
|
|:----------|-----------:|-----------:|--------------------:|-----------------------:|-------------:|-------------------------:|-------------------------:|
|
||||||
|
| AGI/BTC | -0.02 | 0.64 | 5.86 | 0.56 | 3.41 | 14 | 54 |
|
||||||
|
| NXS/BTC | -0.03 | 0.64 | 2.99 | 0.57 | 1.54 | 11 | 26 |
|
||||||
|
| LEND/BTC | -0.02 | 0.82 | 2.05 | 0.22 | 1.50 | 11 | 36 |
|
||||||
|
| VIA/BTC | -0.01 | 0.55 | 3.01 | 0.83 | 1.19 | 11 | 48 |
|
||||||
|
| MTH/BTC | -0.09 | 0.56 | 2.82 | 0.80 | 1.12 | 18 | 52 |
|
||||||
|
| ARDR/BTC | -0.04 | 0.42 | 3.14 | 1.40 | 0.73 | 12 | 42 |
|
||||||
|
| BCPT/BTC | -0.01 | 0.71 | 1.34 | 0.40 | 0.67 | 14 | 30 |
|
||||||
|
| WINGS/BTC | -0.02 | 0.56 | 1.97 | 0.80 | 0.65 | 27 | 42 |
|
||||||
|
| VIBE/BTC | -0.02 | 0.83 | 0.91 | 0.20 | 0.59 | 12 | 35 |
|
||||||
|
| MCO/BTC | -0.02 | 0.79 | 0.97 | 0.27 | 0.55 | 14 | 31 |
|
||||||
|
| GNT/BTC | -0.02 | 0.50 | 2.06 | 1.00 | 0.53 | 18 | 24 |
|
||||||
|
| HOT/BTC | -0.01 | 0.17 | 7.72 | 4.81 | 0.50 | 209 | 7 |
|
||||||
|
| SNM/BTC | -0.03 | 0.71 | 1.06 | 0.42 | 0.45 | 17 | 38 |
|
||||||
|
| APPC/BTC | -0.02 | 0.44 | 2.28 | 1.27 | 0.44 | 25 | 43 |
|
||||||
|
| NEBL/BTC | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 |
|
||||||
|
|
||||||
|
### Update cached pairs with the latest data
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py edge --refresh-pairs-cached
|
||||||
|
```
|
||||||
|
|
||||||
|
### Precising stoploss range
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced use of timerange
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py edge --timerange=20181110-20181113
|
||||||
|
```
|
||||||
|
|
||||||
|
Doing --timerange=-200 will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop.
|
||||||
|
|
||||||
|
The full timerange specification:
|
||||||
|
|
||||||
|
* Use last 123 tickframes of data: --timerange=-123
|
||||||
|
* Use first 123 tickframes of data: --timerange=123-
|
||||||
|
* Use tickframes from line 123 through 456: --timerange=123-456
|
||||||
|
* Use tickframes till 2018/01/31: --timerange=-20180131
|
||||||
|
* Use tickframes since 2018/01/31: --timerange=20180131-
|
||||||
|
* Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301
|
||||||
|
* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600
|
@ -21,6 +21,7 @@ Pull-request. Do not hesitate to reach us on
|
|||||||
- [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
- [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
||||||
- [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
|
- [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
|
||||||
- [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
|
- [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
|
||||||
|
- [Edge commands](https://github.com/mishaker/freqtrade/blob/develop/docs/bot-usage.md#edge-commands)
|
||||||
- [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
|
- [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
|
||||||
- [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy)
|
- [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy)
|
||||||
- [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator)
|
- [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator)
|
||||||
|
@ -128,6 +128,22 @@ class Arguments(object):
|
|||||||
"""
|
"""
|
||||||
Parses given arguments for Backtesting scripts.
|
Parses given arguments for Backtesting scripts.
|
||||||
"""
|
"""
|
||||||
|
parser.add_argument(
|
||||||
|
'--eps', '--enable-position-stacking',
|
||||||
|
help='Allow buying the same pair multiple times (position stacking)',
|
||||||
|
action='store_true',
|
||||||
|
dest='position_stacking',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--dmmp', '--disable-max-market-positions',
|
||||||
|
help='Disable applying `max_open_trades` during backtest '
|
||||||
|
'(same as setting `max_open_trades` to a very high number)',
|
||||||
|
action='store_false',
|
||||||
|
dest='use_max_market_positions',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l', '--live',
|
'-l', '--live',
|
||||||
help='using live data',
|
help='using live data',
|
||||||
@ -171,6 +187,27 @@ class Arguments(object):
|
|||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def edge_options(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
Parses given arguments for Backtesting scripts.
|
||||||
|
"""
|
||||||
|
parser.add_argument(
|
||||||
|
'-r', '--refresh-pairs-cached',
|
||||||
|
help='refresh the pairs files in tests/testdata with the latest data from the '
|
||||||
|
'exchange. Use it if you want to run your edge with up-to-date data.',
|
||||||
|
action='store_true',
|
||||||
|
dest='refresh_pairs',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--stoplosses',
|
||||||
|
help='defines a range of stoploss against which edge will assess the strategy '
|
||||||
|
'the format is "min,max,step" (without any space).'
|
||||||
|
'example: --stoplosses=-0.01,-0.1,-0.001',
|
||||||
|
type=str,
|
||||||
|
dest='stoploss_range',
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
|
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
@ -184,6 +221,20 @@ class Arguments(object):
|
|||||||
dest='ticker_interval',
|
dest='ticker_interval',
|
||||||
type=str,
|
type=str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--timerange',
|
||||||
|
help='specify what timerange of data to use.',
|
||||||
|
default=None,
|
||||||
|
type=str,
|
||||||
|
dest='timerange',
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
Parses given arguments for Hyperopt scripts.
|
||||||
|
"""
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--eps', '--enable-position-stacking',
|
'--eps', '--enable-position-stacking',
|
||||||
help='Allow buying the same pair multiple times (position stacking)',
|
help='Allow buying the same pair multiple times (position stacking)',
|
||||||
@ -200,20 +251,6 @@ class Arguments(object):
|
|||||||
dest='use_max_market_positions',
|
dest='use_max_market_positions',
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--timerange',
|
|
||||||
help='specify what timerange of data to use.',
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
dest='timerange',
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
|
|
||||||
"""
|
|
||||||
Parses given arguments for Hyperopt scripts.
|
|
||||||
"""
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e', '--epochs',
|
'-e', '--epochs',
|
||||||
help='specify number of epochs (default: %(default)d)',
|
help='specify number of epochs (default: %(default)d)',
|
||||||
@ -237,7 +274,7 @@ class Arguments(object):
|
|||||||
Builds and attaches all subcommands
|
Builds and attaches all subcommands
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
from freqtrade.optimize import backtesting, hyperopt
|
from freqtrade.optimize import backtesting, hyperopt, edge_cli
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='subparser')
|
subparsers = self.parser.add_subparsers(dest='subparser')
|
||||||
|
|
||||||
@ -247,6 +284,12 @@ class Arguments(object):
|
|||||||
self.optimizer_shared_options(backtesting_cmd)
|
self.optimizer_shared_options(backtesting_cmd)
|
||||||
self.backtesting_options(backtesting_cmd)
|
self.backtesting_options(backtesting_cmd)
|
||||||
|
|
||||||
|
# Add edge subcommand
|
||||||
|
edge_cmd = subparsers.add_parser('edge', help='edge module')
|
||||||
|
edge_cmd.set_defaults(func=edge_cli.start)
|
||||||
|
self.optimizer_shared_options(edge_cmd)
|
||||||
|
self.edge_options(edge_cmd)
|
||||||
|
|
||||||
# Add hyperopt subcommand
|
# Add hyperopt subcommand
|
||||||
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
|
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
|
||||||
hyperopt_cmd.set_defaults(func=hyperopt.start)
|
hyperopt_cmd.set_defaults(func=hyperopt.start)
|
||||||
|
@ -59,6 +59,9 @@ class Configuration(object):
|
|||||||
# Load Backtesting
|
# Load Backtesting
|
||||||
config = self._load_backtesting_config(config)
|
config = self._load_backtesting_config(config)
|
||||||
|
|
||||||
|
# Load Edge
|
||||||
|
config = self._load_edge_config(config)
|
||||||
|
|
||||||
# Load Hyperopt
|
# Load Hyperopt
|
||||||
config = self._load_hyperopt_config(config)
|
config = self._load_hyperopt_config(config)
|
||||||
|
|
||||||
@ -218,6 +221,32 @@ class Configuration(object):
|
|||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def _load_edge_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Extract information for sys.argv and load Edge configuration
|
||||||
|
:return: configuration as dictionary
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If --timerange is used we add it to the configuration
|
||||||
|
if 'timerange' in self.args and self.args.timerange:
|
||||||
|
config.update({'timerange': self.args.timerange})
|
||||||
|
logger.info('Parameter --timerange detected: %s ...', self.args.timerange)
|
||||||
|
|
||||||
|
# If --timerange is used we add it to the configuration
|
||||||
|
if 'stoploss_range' in self.args and self.args.stoploss_range:
|
||||||
|
txt_range = eval(self.args.stoploss_range)
|
||||||
|
config['edge'].update({'stoploss_range_min': txt_range[0]})
|
||||||
|
config['edge'].update({'stoploss_range_max': txt_range[1]})
|
||||||
|
config['edge'].update({'stoploss_range_step': txt_range[2]})
|
||||||
|
logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range)
|
||||||
|
|
||||||
|
# If -r/--refresh-pairs-cached is used we add it to the configuration
|
||||||
|
if 'refresh_pairs' in self.args and self.args.refresh_pairs:
|
||||||
|
config.update({'refresh_pairs': True})
|
||||||
|
logger.info('Parameter -r/--refresh-pairs-cached detected ...')
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load Hyperopt configuration
|
Extract information for sys.argv and load Hyperopt configuration
|
||||||
|
@ -33,7 +33,9 @@ class Edge():
|
|||||||
# pair info data type
|
# pair info data type
|
||||||
_pair_info = namedtuple(
|
_pair_info = namedtuple(
|
||||||
'pair_info',
|
'pair_info',
|
||||||
['stoploss', 'winrate', 'risk_reward_ratio', 'required_risk_reward', 'expectancy'])
|
['stoploss', 'winrate', 'risk_reward_ratio', 'required_risk_reward', 'expectancy',
|
||||||
|
'nb_trades', 'avg_trade_duration']
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None:
|
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None:
|
||||||
|
|
||||||
@ -53,6 +55,7 @@ class Edge():
|
|||||||
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
||||||
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
||||||
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
||||||
|
self._refresh_pairs = True
|
||||||
|
|
||||||
self._stoploss_range_min = float(self.edge_config.get('stoploss_range_min', -0.01))
|
self._stoploss_range_min = float(self.edge_config.get('stoploss_range_min', -0.01))
|
||||||
self._stoploss_range_max = float(self.edge_config.get('stoploss_range_max', -0.05))
|
self._stoploss_range_max = float(self.edge_config.get('stoploss_range_max', -0.05))
|
||||||
@ -86,7 +89,7 @@ class Edge():
|
|||||||
self.config['datadir'],
|
self.config['datadir'],
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval=self.ticker_interval,
|
ticker_interval=self.ticker_interval,
|
||||||
refresh_pairs=True,
|
refresh_pairs=self._refresh_pairs,
|
||||||
exchange=self.exchange,
|
exchange=self.exchange,
|
||||||
timerange=self._timerange
|
timerange=self._timerange
|
||||||
)
|
)
|
||||||
@ -296,7 +299,9 @@ class Edge():
|
|||||||
'winrate': x.winrate,
|
'winrate': x.winrate,
|
||||||
'risk_reward_ratio': x.risk_reward_ratio,
|
'risk_reward_ratio': x.risk_reward_ratio,
|
||||||
'required_risk_reward': x.required_risk_reward,
|
'required_risk_reward': x.required_risk_reward,
|
||||||
'expectancy': x.expectancy
|
'expectancy': x.expectancy,
|
||||||
|
'nb_trades': x.nb_trades,
|
||||||
|
'avg_trade_duration': x.avg_trade_duration
|
||||||
}
|
}
|
||||||
final[x.pair] = self._pair_info(**info)
|
final[x.pair] = self._pair_info(**info)
|
||||||
|
|
||||||
|
106
freqtrade/optimize/edge_cli.py
Normal file
106
freqtrade/optimize/edge_cli.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, W0212, too-many-arguments
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains the backtesting logic
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from argparse import Namespace
|
||||||
|
from typing import Dict, Any
|
||||||
|
from tabulate import tabulate
|
||||||
|
from freqtrade.edge import Edge
|
||||||
|
|
||||||
|
from freqtrade.configuration import Configuration
|
||||||
|
from freqtrade.arguments import Arguments
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
from freqtrade.strategy.resolver import StrategyResolver
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeCli(object):
|
||||||
|
"""
|
||||||
|
Backtesting class, this class contains all the logic to run a backtest
|
||||||
|
|
||||||
|
To run a backtest:
|
||||||
|
backtesting = Backtesting(config)
|
||||||
|
backtesting.start()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
# Reset keys for edge
|
||||||
|
self.config['exchange']['key'] = ''
|
||||||
|
self.config['exchange']['secret'] = ''
|
||||||
|
self.config['exchange']['password'] = ''
|
||||||
|
self.config['exchange']['uid'] = ''
|
||||||
|
self.config['dry_run'] = True
|
||||||
|
self.exchange = Exchange(self.config)
|
||||||
|
self.strategy = StrategyResolver(self.config).strategy
|
||||||
|
|
||||||
|
self.edge = Edge(config, self.exchange, self.strategy)
|
||||||
|
self.edge._refresh_pairs = self.config.get('refresh_pairs', False)
|
||||||
|
|
||||||
|
self.timerange = Arguments.parse_timerange(None if self.config.get(
|
||||||
|
'timerange') is None else str(self.config.get('timerange')))
|
||||||
|
|
||||||
|
self.edge._timerange = self.timerange
|
||||||
|
|
||||||
|
def _generate_edge_table(self, results: dict) -> str:
|
||||||
|
|
||||||
|
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')
|
||||||
|
tabular_data = []
|
||||||
|
headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio',
|
||||||
|
'required risk reward', 'expectancy', 'total number of trades',
|
||||||
|
'average duration (min)']
|
||||||
|
|
||||||
|
for result in results.items():
|
||||||
|
if result[1].nb_trades > 0:
|
||||||
|
tabular_data.append([
|
||||||
|
result[0],
|
||||||
|
result[1].stoploss,
|
||||||
|
result[1].winrate,
|
||||||
|
result[1].risk_reward_ratio,
|
||||||
|
result[1].required_risk_reward,
|
||||||
|
result[1].expectancy,
|
||||||
|
result[1].nb_trades,
|
||||||
|
round(result[1].avg_trade_duration)
|
||||||
|
])
|
||||||
|
|
||||||
|
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
self.edge.calculate()
|
||||||
|
print('') # blank like for readability
|
||||||
|
print(self._generate_edge_table(self.edge._cached_pairs))
|
||||||
|
|
||||||
|
|
||||||
|
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Prepare the configuration for the backtesting
|
||||||
|
:param args: Cli args from Arguments()
|
||||||
|
:return: Configuration
|
||||||
|
"""
|
||||||
|
configuration = Configuration(args)
|
||||||
|
config = configuration.get_config()
|
||||||
|
|
||||||
|
# Ensure we do not use Exchange credentials
|
||||||
|
config['exchange']['key'] = ''
|
||||||
|
config['exchange']['secret'] = ''
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def start(args: Namespace) -> None:
|
||||||
|
"""
|
||||||
|
Start Edge script
|
||||||
|
:param args: Cli args from Arguments()
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# Initialize configuration
|
||||||
|
config = setup_configuration(args)
|
||||||
|
logger.info('Starting freqtrade in Edge mode')
|
||||||
|
|
||||||
|
# Initialize Edge object
|
||||||
|
edge_cli = EdgeCli(config)
|
||||||
|
edge_cli.start()
|
@ -128,9 +128,9 @@ def test_adjust(mocker, default_conf):
|
|||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71)
|
'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -143,9 +143,9 @@ def test_stoploss(mocker, default_conf):
|
|||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71),
|
'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71)
|
'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
140
freqtrade/tests/optimize/test_edge_cli.py
Normal file
140
freqtrade/tests/optimize/test_edge_cli.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, C0103, C0330
|
||||||
|
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
import json
|
||||||
|
from typing import List
|
||||||
|
from freqtrade.edge import Edge
|
||||||
|
from freqtrade.arguments import Arguments
|
||||||
|
from freqtrade.optimize.edge_cli import (EdgeCli, setup_configuration, start)
|
||||||
|
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||||
|
|
||||||
|
|
||||||
|
def get_args(args) -> List[str]:
|
||||||
|
return Arguments(args, '').get_parsed_arg()
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(default_conf)
|
||||||
|
))
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--strategy', 'DefaultStrategy',
|
||||||
|
'edge'
|
||||||
|
]
|
||||||
|
|
||||||
|
config = setup_configuration(get_args(args))
|
||||||
|
assert 'max_open_trades' in config
|
||||||
|
assert 'stake_currency' in config
|
||||||
|
assert 'stake_amount' in config
|
||||||
|
assert 'exchange' in config
|
||||||
|
assert 'pair_whitelist' in config['exchange']
|
||||||
|
assert 'datadir' in config
|
||||||
|
assert log_has(
|
||||||
|
'Using data folder: {} ...'.format(config['datadir']),
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
assert 'ticker_interval' in config
|
||||||
|
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||||
|
|
||||||
|
assert 'refresh_pairs' not in config
|
||||||
|
assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||||
|
|
||||||
|
assert 'timerange' not in config
|
||||||
|
assert 'stoploss_range' not in config
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(edge_conf)
|
||||||
|
))
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--strategy', 'DefaultStrategy',
|
||||||
|
'--datadir', '/foo/bar',
|
||||||
|
'edge',
|
||||||
|
'--ticker-interval', '1m',
|
||||||
|
'--refresh-pairs-cached',
|
||||||
|
'--timerange', ':100',
|
||||||
|
'--stoplosses=-0.01,-0.10,-0.001'
|
||||||
|
]
|
||||||
|
|
||||||
|
config = setup_configuration(get_args(args))
|
||||||
|
assert 'max_open_trades' in config
|
||||||
|
assert 'stake_currency' in config
|
||||||
|
assert 'stake_amount' in config
|
||||||
|
assert 'exchange' in config
|
||||||
|
assert 'pair_whitelist' in config['exchange']
|
||||||
|
assert 'datadir' in config
|
||||||
|
assert log_has(
|
||||||
|
'Using data folder: {} ...'.format(config['datadir']),
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
assert 'ticker_interval' in config
|
||||||
|
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||||
|
assert log_has(
|
||||||
|
'Using ticker_interval: 1m ...',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'refresh_pairs' in config
|
||||||
|
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||||
|
assert 'timerange' in config
|
||||||
|
assert log_has(
|
||||||
|
'Parameter --timerange detected: {} ...'.format(config['timerange']),
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||||
|
start_mock = MagicMock()
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock)
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(edge_conf)
|
||||||
|
))
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--strategy', 'DefaultStrategy',
|
||||||
|
'edge'
|
||||||
|
]
|
||||||
|
args = get_args(args)
|
||||||
|
start(args)
|
||||||
|
assert log_has(
|
||||||
|
'Starting freqtrade in Edge mode',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
assert start_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_init(mocker, edge_conf) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
|
edge_cli = EdgeCli(edge_conf)
|
||||||
|
assert edge_cli.config == edge_conf
|
||||||
|
assert callable(edge_cli.edge.calculate)
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_edge_table(edge_conf, mocker):
|
||||||
|
patch_exchange(mocker)
|
||||||
|
edge_cli = EdgeCli(edge_conf)
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
info = {
|
||||||
|
'stoploss': -0.01,
|
||||||
|
'winrate': 0.60,
|
||||||
|
'risk_reward_ratio': 2,
|
||||||
|
'required_risk_reward': 1,
|
||||||
|
'expectancy': 3,
|
||||||
|
'nb_trades': 10,
|
||||||
|
'avg_trade_duration': 60
|
||||||
|
}
|
||||||
|
|
||||||
|
results['ETH/BTC'] = Edge._pair_info(**info)
|
||||||
|
assert edge_cli._generate_edge_table(results).count(':|') == 7
|
||||||
|
assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1
|
||||||
|
assert edge_cli._generate_edge_table(results).count(
|
||||||
|
'| risk reward ratio | required risk reward | expectancy |') == 1
|
Loading…
Reference in New Issue
Block a user