Merge branch 'develop' into pr/eatrisno/4308
This commit is contained in:
commit
7ff794cb87
@ -19,7 +19,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--enable-protections]
|
[--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
[--export EXPORT] [--export-filename PATH]
|
[--export {none,trades}] [--export-filename PATH]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -63,8 +63,8 @@ optional arguments:
|
|||||||
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-
|
||||||
DefaultStrategy.json`
|
DefaultStrategy.json`
|
||||||
--export EXPORT Export backtest results, argument are: trades.
|
--export {none,trades}
|
||||||
Example: `--export=trades`
|
Export backtest results (default: trades).
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
Save backtest results to the file with this filename.
|
Save backtest results to the file with this filename.
|
||||||
Requires `--export` to be set as well. Example:
|
Requires `--export` to be set as well. Example:
|
||||||
@ -100,7 +100,7 @@ Strategy arguments:
|
|||||||
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
||||||
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||||
|
|
||||||
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default.
|
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/<exchange>` by default.
|
||||||
If no data is available for the exchange / pair / timeframe combination, backtesting will ask you to download them first using `freqtrade download-data`.
|
If no data is available for the exchange / pair / timeframe combination, backtesting will ask you to download them first using `freqtrade download-data`.
|
||||||
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
|
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
|
||||||
|
|
||||||
@ -110,11 +110,16 @@ All profit calculations include fees, and freqtrade will use the exchange's defa
|
|||||||
|
|
||||||
!!! Warning "Using dynamic pairlists for backtesting"
|
!!! Warning "Using dynamic pairlists for backtesting"
|
||||||
Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
|
Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
|
||||||
Also, when using pairlists other than StaticPairlist, reproducability of backtesting-results cannot be guaranteed.
|
Also, when using pairlists other than StaticPairlist, reproducibility of backtesting-results cannot be guaranteed.
|
||||||
Please read the [pairlists documentation](plugins.md#pairlists) for more information.
|
Please read the [pairlists documentation](plugins.md#pairlists) for more information.
|
||||||
|
|
||||||
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist.
|
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
By default, Freqtrade will export backtesting results to `user_data/backtest_results`.
|
||||||
|
The exported trades can be used for [further analysis](#further-backtest-result-analysis) or can be used by the [plotting sub-command](plotting.md#plot-price-and-indicators) (`freqtrade plot-dataframe`) in the scripts directory.
|
||||||
|
|
||||||
|
|
||||||
### Starting balance
|
### Starting balance
|
||||||
|
|
||||||
Backtesting will require a starting balance, which can be provided as `--dry-run-wallet <balance>` or `--starting-balance <balance>` command line argument, or via `dry_run_wallet` configuration setting.
|
Backtesting will require a starting balance, which can be provided as `--dry-run-wallet <balance>` or `--starting-balance <balance>` command line argument, or via `dry_run_wallet` configuration setting.
|
||||||
@ -174,13 +179,13 @@ Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Exporting trades to file
|
Prevent exporting trades to file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade backtesting --strategy backtesting --export trades --config config.json
|
freqtrade backtesting --strategy backtesting --export none --config config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts directory.
|
Only use this if you're sure you'll not want to plot or analyze your results further.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -279,7 +284,7 @@ A backtesting result will look like that:
|
|||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total trades | 429 |
|
| Total/Daily Avg Trades| 429 / 3.575 |
|
||||||
| Starting balance | 0.01000000 BTC |
|
| Starting balance | 0.01000000 BTC |
|
||||||
| Final balance | 0.01762792 BTC |
|
| Final balance | 0.01762792 BTC |
|
||||||
| Absolute profit | 0.00762792 BTC |
|
| Absolute profit | 0.00762792 BTC |
|
||||||
@ -368,12 +373,11 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total trades | 429 |
|
| Total/Daily Avg Trades| 429 / 3.575 |
|
||||||
| Starting balance | 0.01000000 BTC |
|
| Starting balance | 0.01000000 BTC |
|
||||||
| Final balance | 0.01762792 BTC |
|
| Final balance | 0.01762792 BTC |
|
||||||
| Absolute profit | 0.00762792 BTC |
|
| Absolute profit | 0.00762792 BTC |
|
||||||
| Total profit % | 76.2% |
|
| Total profit % | 76.2% |
|
||||||
| Trades per day | 3.575 |
|
|
||||||
| Avg. stake amount | 0.001 BTC |
|
| Avg. stake amount | 0.001 BTC |
|
||||||
| Total trade volume | 0.429 BTC |
|
| Total trade volume | 0.429 BTC |
|
||||||
| | |
|
| | |
|
||||||
@ -404,12 +408,11 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
|
|
||||||
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
||||||
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
||||||
- `Total trades`: Identical to the total trades of the backtest output table.
|
- `Total/Daily Avg Trades`: Identical to the total trades of the backtest output table / Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
||||||
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
||||||
- `Final balance`: Final balance - starting balance + absolute profit.
|
- `Final balance`: Final balance - starting balance + absolute profit.
|
||||||
- `Absolute profit`: Profit made in stake currency.
|
- `Absolute profit`: Profit made in stake currency.
|
||||||
- `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital − Starting capital) / Starting capital`.
|
- `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital − Starting capital) / Starting capital`.
|
||||||
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
|
||||||
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
|
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
|
||||||
- `Total trade volume`: Volume generated on the exchange to reach the above profit.
|
- `Total trade volume`: Volume generated on the exchange to reach the above profit.
|
||||||
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
||||||
@ -441,6 +444,7 @@ Since backtesting lacks some detailed information about what happens within a ca
|
|||||||
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes
|
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes
|
||||||
- Low happens before high for stoploss, protecting capital first
|
- Low happens before high for stoploss, protecting capital first
|
||||||
- Trailing stoploss
|
- Trailing stoploss
|
||||||
|
- Trailing Stoploss is only adjusted if it's below the candle's low (otherwise it would be triggered)
|
||||||
- High happens first - adjusting stoploss
|
- High happens first - adjusting stoploss
|
||||||
- Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly)
|
- Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly)
|
||||||
- ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies
|
- ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
mkdocs==1.2
|
mkdocs==1.2.1
|
||||||
mkdocs-material==7.1.7
|
mkdocs-material==7.1.8
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==8.2
|
pymdown-extensions==8.2
|
||||||
|
@ -167,8 +167,9 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
),
|
),
|
||||||
"export": Arg(
|
"export": Arg(
|
||||||
'--export',
|
'--export',
|
||||||
help='Export backtest results, argument are: trades. '
|
help='Export backtest results (default: trades).',
|
||||||
'Example: `--export=trades`',
|
choices=constants.EXPORT_OPTIONS,
|
||||||
|
|
||||||
),
|
),
|
||||||
"exportfilename": Arg(
|
"exportfilename": Arg(
|
||||||
'--export-filename',
|
'--export-filename',
|
||||||
|
@ -12,6 +12,7 @@ PROCESS_THROTTLE_SECS = 5 # sec
|
|||||||
HYPEROPT_EPOCH = 100 # epochs
|
HYPEROPT_EPOCH = 100 # epochs
|
||||||
RETRY_TIMEOUT = 30 # sec
|
RETRY_TIMEOUT = 30 # sec
|
||||||
TIMEOUT_UNITS = ['minutes', 'seconds']
|
TIMEOUT_UNITS = ['minutes', 'seconds']
|
||||||
|
EXPORT_OPTIONS = ['none', 'trades']
|
||||||
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
||||||
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
||||||
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||||
@ -308,6 +309,7 @@ CONF_SCHEMA = {
|
|||||||
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
||||||
},
|
},
|
||||||
'db_url': {'type': 'string'},
|
'db_url': {'type': 'string'},
|
||||||
|
'export': {'type': 'string', 'enum': EXPORT_OPTIONS, 'default': 'trades'},
|
||||||
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
|
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
|
||||||
'forcebuy_enable': {'type': 'boolean'},
|
'forcebuy_enable': {'type': 'boolean'},
|
||||||
'disable_dataframe_checks': {'type': 'boolean'},
|
'disable_dataframe_checks': {'type': 'boolean'},
|
||||||
|
@ -225,6 +225,22 @@ class Backtesting:
|
|||||||
# sell at open price.
|
# sell at open price.
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
|
# Special case: trailing triggers within same candle as trade opened. Assume most
|
||||||
|
# pessimistic price movement, which is moving just enough to arm stoploss and
|
||||||
|
# immediately going down to stop price.
|
||||||
|
if (sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0
|
||||||
|
and self.strategy.trailing_stop_positive):
|
||||||
|
if self.strategy.trailing_only_offset_is_reached:
|
||||||
|
# Worst case: price reaches stop_positive_offset and dives down.
|
||||||
|
stop_rate = (sell_row[OPEN_IDX] *
|
||||||
|
(1 + abs(self.strategy.trailing_stop_positive_offset) -
|
||||||
|
abs(self.strategy.trailing_stop_positive)))
|
||||||
|
else:
|
||||||
|
# Worst case: price ticks tiny bit above open and dives down.
|
||||||
|
stop_rate = sell_row[OPEN_IDX] * (1 - abs(self.strategy.trailing_stop_positive))
|
||||||
|
assert stop_rate < sell_row[HIGH_IDX]
|
||||||
|
return stop_rate
|
||||||
|
|
||||||
# Set close_rate to stoploss
|
# Set close_rate to stoploss
|
||||||
return trade.stop_loss
|
return trade.stop_loss
|
||||||
elif sell.sell_type == (SellType.ROI):
|
elif sell.sell_type == (SellType.ROI):
|
||||||
@ -520,7 +536,7 @@ class Backtesting:
|
|||||||
stats = generate_backtest_stats(data, self.all_results,
|
stats = generate_backtest_stats(data, self.all_results,
|
||||||
min_date=min_date, max_date=max_date)
|
min_date=min_date, max_date=max_date)
|
||||||
|
|
||||||
if self.config.get('export', False):
|
if self.config.get('export', 'none') == 'trades':
|
||||||
store_backtest_stats(self.config['exportfilename'], stats)
|
store_backtest_stats(self.config['exportfilename'], stats)
|
||||||
|
|
||||||
# Show backtest results
|
# Show backtest results
|
||||||
|
@ -556,7 +556,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
('Backtesting to', strat_results['backtest_end']),
|
('Backtesting to', strat_results['backtest_end']),
|
||||||
('Max open trades', strat_results['max_open_trades']),
|
('Max open trades', strat_results['max_open_trades']),
|
||||||
('', ''), # Empty line to improve readability
|
('', ''), # Empty line to improve readability
|
||||||
('Total trades', strat_results['total_trades']),
|
('Total/Daily Avg Trades',
|
||||||
|
f"{strat_results['total_trades']} / {strat_results['trades_per_day']}"),
|
||||||
('Starting balance', round_coin_value(strat_results['starting_balance'],
|
('Starting balance', round_coin_value(strat_results['starting_balance'],
|
||||||
strat_results['stake_currency'])),
|
strat_results['stake_currency'])),
|
||||||
('Final balance', round_coin_value(strat_results['final_balance'],
|
('Final balance', round_coin_value(strat_results['final_balance'],
|
||||||
@ -564,7 +565,6 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
|||||||
('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. 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'],
|
||||||
|
@ -58,6 +58,9 @@ class IResolver:
|
|||||||
# Generate spec based on absolute path
|
# Generate spec based on absolute path
|
||||||
# Pass object_name as first argument to have logging print a reasonable name.
|
# Pass object_name as first argument to have logging print a reasonable name.
|
||||||
spec = importlib.util.spec_from_file_location(object_name or "", str(module_path))
|
spec = importlib.util.spec_from_file_location(object_name or "", str(module_path))
|
||||||
|
if not spec:
|
||||||
|
return iter([None])
|
||||||
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
try:
|
try:
|
||||||
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
|
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
|
||||||
@ -91,6 +94,9 @@ class IResolver:
|
|||||||
if not str(entry).endswith('.py'):
|
if not str(entry).endswith('.py'):
|
||||||
logger.debug('Ignoring %s', entry)
|
logger.debug('Ignoring %s', entry)
|
||||||
continue
|
continue
|
||||||
|
if entry.is_symlink() and not entry.is_file():
|
||||||
|
logger.debug('Ignoring broken symlink %s', entry)
|
||||||
|
continue
|
||||||
module_path = entry.resolve()
|
module_path = entry.resolve()
|
||||||
|
|
||||||
obj = next(cls._get_valid_object(module_path, object_name), None)
|
obj = next(cls._get_valid_object(module_path, object_name), None)
|
||||||
|
@ -60,7 +60,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
|
|||||||
)
|
)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
logger.info(
|
logger.debug(
|
||||||
'Executing handler: %s for chat_id: %s',
|
'Executing handler: %s for chat_id: %s',
|
||||||
command_handler.__name__,
|
command_handler.__name__,
|
||||||
chat_id
|
chat_id
|
||||||
|
@ -77,14 +77,13 @@ class Webhook(RPCHandler):
|
|||||||
def _send_msg(self, payload: dict) -> None:
|
def _send_msg(self, payload: dict) -> None:
|
||||||
"""do the actual call to the webhook"""
|
"""do the actual call to the webhook"""
|
||||||
|
|
||||||
if self._format == 'form':
|
|
||||||
kwargs = {'data': payload}
|
|
||||||
elif self._format == 'json':
|
|
||||||
kwargs = {'json': payload}
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Unknown format: {}'.format(self._format))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
post(self._url, **kwargs)
|
if self._format == 'form':
|
||||||
|
post(self._url, data=payload)
|
||||||
|
elif self._format == 'json':
|
||||||
|
post(self._url, json=payload)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Unknown format: {}'.format(self._format))
|
||||||
|
|
||||||
except RequestException as exc:
|
except RequestException as exc:
|
||||||
logger.warning("Could not call webhook url. Exception: %s", exc)
|
logger.warning("Could not call webhook url. Exception: %s", exc)
|
||||||
|
@ -524,15 +524,14 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
:param force_stoploss: Externally provided stoploss
|
:param force_stoploss: Externally provided stoploss
|
||||||
:return: True if trade should be sold, False otherwise
|
:return: True if trade should be sold, False otherwise
|
||||||
"""
|
"""
|
||||||
# Set current rate to low for backtesting sell
|
current_rate = rate
|
||||||
current_rate = low or rate
|
|
||||||
current_profit = trade.calc_profit_ratio(current_rate)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
trade.adjust_min_max_rates(high or current_rate)
|
trade.adjust_min_max_rates(high or current_rate)
|
||||||
|
|
||||||
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
||||||
current_time=date, current_profit=current_profit,
|
current_time=date, current_profit=current_profit,
|
||||||
force_stoploss=force_stoploss, high=high)
|
force_stoploss=force_stoploss, low=low, high=high)
|
||||||
|
|
||||||
# Set current rate to high for backtesting sell
|
# Set current rate to high for backtesting sell
|
||||||
current_rate = high or rate
|
current_rate = high or rate
|
||||||
@ -599,18 +598,21 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
||||||
current_time: datetime, current_profit: float,
|
current_time: datetime, current_profit: float,
|
||||||
force_stoploss: float, high: float = None) -> SellCheckTuple:
|
force_stoploss: float, low: float = None,
|
||||||
|
high: float = None) -> SellCheckTuple:
|
||||||
"""
|
"""
|
||||||
Based on current profit of the trade and configured (trailing) stoploss,
|
Based on current profit of the trade and configured (trailing) stoploss,
|
||||||
decides to sell or not
|
decides to sell or not
|
||||||
:param current_profit: current profit as ratio
|
:param current_profit: current profit as ratio
|
||||||
|
:param low: Low value of this candle, only set in backtesting
|
||||||
|
:param high: High value of this candle, only set in backtesting
|
||||||
"""
|
"""
|
||||||
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
||||||
|
|
||||||
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
|
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
|
||||||
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
||||||
|
|
||||||
if self.use_custom_stoploss:
|
if self.use_custom_stoploss and trade.stop_loss < (low or current_rate):
|
||||||
stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
|
stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
|
||||||
)(pair=trade.pair, trade=trade,
|
)(pair=trade.pair, trade=trade,
|
||||||
current_time=current_time,
|
current_time=current_time,
|
||||||
@ -623,7 +625,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
else:
|
else:
|
||||||
logger.warning("CustomStoploss function did not return valid stoploss")
|
logger.warning("CustomStoploss function did not return valid stoploss")
|
||||||
|
|
||||||
if self.trailing_stop:
|
if self.trailing_stop and trade.stop_loss < (low or current_rate):
|
||||||
# trailing stoploss handling
|
# trailing stoploss handling
|
||||||
sl_offset = self.trailing_stop_positive_offset
|
sl_offset = self.trailing_stop_positive_offset
|
||||||
|
|
||||||
@ -643,7 +645,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
# evaluate if the stoploss was hit if stoploss is not on exchange
|
# evaluate if the stoploss was hit if stoploss is not on exchange
|
||||||
# in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
|
# in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
|
||||||
# regular stoploss handling.
|
# regular stoploss handling.
|
||||||
if ((trade.stop_loss >= current_rate) and
|
if ((trade.stop_loss >= (low or current_rate)) and
|
||||||
(not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):
|
(not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):
|
||||||
|
|
||||||
sell_type = SellType.STOP_LOSS
|
sell_type = SellType.STOP_LOSS
|
||||||
@ -652,7 +654,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
if trade.initial_stop_loss != trade.stop_loss:
|
if trade.initial_stop_loss != trade.stop_loss:
|
||||||
sell_type = SellType.TRAILING_STOP_LOSS
|
sell_type = SellType.TRAILING_STOP_LOSS
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{trade.pair} - HIT STOP: current price at {current_rate:.6f}, "
|
f"{trade.pair} - HIT STOP: current price at {(low or current_rate):.6f}, "
|
||||||
f"stoploss is {trade.stop_loss:.6f}, "
|
f"stoploss is {trade.stop_loss:.6f}, "
|
||||||
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
|
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
|
||||||
f"trade opened at {trade.open_rate:.6f}")
|
f"trade opened at {trade.open_rate:.6f}")
|
||||||
|
@ -7,7 +7,7 @@ coveralls==3.1.0
|
|||||||
flake8==3.9.2
|
flake8==3.9.2
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==4.3.0
|
flake8-tidy-imports==4.3.0
|
||||||
mypy==0.812
|
mypy==0.902
|
||||||
pytest==6.2.4
|
pytest==6.2.4
|
||||||
pytest-asyncio==0.15.1
|
pytest-asyncio==0.15.1
|
||||||
pytest-cov==2.12.1
|
pytest-cov==2.12.1
|
||||||
@ -17,3 +17,9 @@ isort==5.8.0
|
|||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==6.0.7
|
nbconvert==6.0.7
|
||||||
|
|
||||||
|
# mypy types
|
||||||
|
types-cachetools==0.1.7
|
||||||
|
types-filelock==0.1.3
|
||||||
|
types-requests==0.1.11
|
||||||
|
types-tabulate==0.1.0
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
numpy==1.20.3
|
numpy==1.20.3
|
||||||
pandas==1.2.4
|
pandas==1.2.4
|
||||||
|
|
||||||
ccxt==1.51.3
|
ccxt==1.51.40
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==3.4.7
|
cryptography==3.4.7
|
||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
SQLAlchemy==1.4.17
|
SQLAlchemy==1.4.18
|
||||||
python-telegram-bot==13.6
|
python-telegram-bot==13.6
|
||||||
arrow==1.1.0
|
arrow==1.1.0
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
|
@ -326,6 +326,7 @@ def get_default_conf(testdatadir):
|
|||||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "DefaultStrategy",
|
||||||
"internals": {},
|
"internals": {},
|
||||||
|
"export": "none",
|
||||||
}
|
}
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
|
@ -457,6 +457,50 @@ tc28 = BTContainer(data=[
|
|||||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
|
||||||
|
# high of stoploss candle.
|
||||||
|
# stop-loss: 10%, ROI: 10% (should not apply)
|
||||||
|
tc29 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||||
|
[1, 5000, 5050, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
|
[2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss
|
||||||
|
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||||
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
|
||||||
|
trailing_stop_positive=0.03,
|
||||||
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test 30: trailing_stop should be triggered immediately on trade open candle.
|
||||||
|
# stop-loss: 10%, ROI: 10% (should not apply)
|
||||||
|
tc30 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||||
|
[1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
|
||||||
|
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||||
|
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||||
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
|
||||||
|
trailing_stop_positive=0.01,
|
||||||
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test 31: trailing_stop should be triggered immediately on trade open candle.
|
||||||
|
# stop-loss: 10%, ROI: 10% (should not apply)
|
||||||
|
tc31 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||||
|
[1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
|
||||||
|
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||||
|
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||||
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
|
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True,
|
||||||
|
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||||
|
trailing_stop_positive=0.01,
|
||||||
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
|
||||||
|
)
|
||||||
|
|
||||||
TESTS = [
|
TESTS = [
|
||||||
tc0,
|
tc0,
|
||||||
tc1,
|
tc1,
|
||||||
@ -487,6 +531,9 @@ TESTS = [
|
|||||||
tc26,
|
tc26,
|
||||||
tc27,
|
tc27,
|
||||||
tc28,
|
tc28,
|
||||||
|
tc29,
|
||||||
|
tc30,
|
||||||
|
tc31,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,6 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
|
'--export', 'none'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
@ -172,7 +173,8 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
|
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
|
||||||
|
|
||||||
assert 'timerange' not in config
|
assert 'timerange' not in config
|
||||||
assert 'export' not in config
|
assert 'export' in config
|
||||||
|
assert config['export'] == 'none'
|
||||||
assert 'runmode' in config
|
assert 'runmode' in config
|
||||||
assert config['runmode'] == RunMode.BACKTEST
|
assert config['runmode'] == RunMode.BACKTEST
|
||||||
|
|
||||||
@ -193,7 +195,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
'--export', '/bar/foo',
|
|
||||||
'--export-filename', 'foo_bar.json',
|
'--export-filename', 'foo_bar.json',
|
||||||
'--fee', '0',
|
'--fee', '0',
|
||||||
]
|
]
|
||||||
@ -223,7 +224,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
|||||||
assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog)
|
assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog)
|
||||||
|
|
||||||
assert 'export' in config
|
assert 'export' in config
|
||||||
assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog)
|
|
||||||
assert 'exportfilename' in config
|
assert 'exportfilename' in config
|
||||||
assert isinstance(config['exportfilename'], Path)
|
assert isinstance(config['exportfilename'], Path)
|
||||||
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
|
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
|
||||||
@ -395,7 +395,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
|
|||||||
|
|
||||||
default_conf['timeframe'] = "1m"
|
default_conf['timeframe'] = "1m"
|
||||||
default_conf['datadir'] = testdatadir
|
default_conf['datadir'] = testdatadir
|
||||||
default_conf['export'] = None
|
default_conf['export'] = 'none'
|
||||||
default_conf['timerange'] = '20180101-20180102'
|
default_conf['timerange'] = '20180101-20180102'
|
||||||
|
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
@ -416,7 +416,7 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
|
|||||||
|
|
||||||
default_conf['timeframe'] = "1m"
|
default_conf['timeframe'] = "1m"
|
||||||
default_conf['datadir'] = testdatadir
|
default_conf['datadir'] = testdatadir
|
||||||
default_conf['export'] = None
|
default_conf['export'] = 'none'
|
||||||
default_conf['timerange'] = '20180101-20180102'
|
default_conf['timerange'] = '20180101-20180102'
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match='No pair in whitelist.'):
|
with pytest.raises(OperationalException, match='No pair in whitelist.'):
|
||||||
@ -440,7 +440,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
|||||||
|
|
||||||
default_conf['ticker_interval'] = "1m"
|
default_conf['ticker_interval'] = "1m"
|
||||||
default_conf['datadir'] = testdatadir
|
default_conf['datadir'] = testdatadir
|
||||||
default_conf['export'] = None
|
default_conf['export'] = 'none'
|
||||||
# Use stoploss from strategy
|
# Use stoploss from strategy
|
||||||
del default_conf['stoploss']
|
del default_conf['stoploss']
|
||||||
default_conf['timerange'] = '20180101-20180102'
|
default_conf['timerange'] = '20180101-20180102'
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# pragma pylint: disable=protected-access, unused-argument, invalid-name
|
# pragma pylint: disable=protected-access, unused-argument, invalid-name
|
||||||
# pragma pylint: disable=too-many-lines, too-many-arguments
|
# pragma pylint: disable=too-many-lines, too-many-arguments
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
@ -112,7 +113,7 @@ def test_cleanup(default_conf, mocker, ) -> None:
|
|||||||
|
|
||||||
def test_authorized_only(default_conf, mocker, caplog, update) -> None:
|
def test_authorized_only(default_conf, mocker, caplog, update) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
bot = FreqtradeBot(default_conf)
|
bot = FreqtradeBot(default_conf)
|
||||||
rpc = RPC(bot)
|
rpc = RPC(bot)
|
||||||
@ -128,6 +129,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
|
|||||||
|
|
||||||
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
chat = Chat(0xdeadbeef, 0)
|
chat = Chat(0xdeadbeef, 0)
|
||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
update.message = Message(randint(1, 100), datetime.utcnow(), chat)
|
update.message = Message(randint(1, 100), datetime.utcnow(), chat)
|
||||||
|
@ -425,7 +425,6 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
|
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
|
||||||
|
|
||||||
assert 'timerange' not in config
|
assert 'timerange' not in config
|
||||||
assert 'export' not in config
|
|
||||||
|
|
||||||
|
|
||||||
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
|
||||||
@ -448,7 +447,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
'--export', '/bar/foo',
|
'--export', 'trades',
|
||||||
'--stake-amount', 'unlimited'
|
'--stake-amount', 'unlimited'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -496,7 +495,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
|||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--export', '/bar/foo',
|
'--export', 'trades',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'DefaultStrategy',
|
||||||
'TestStrategy'
|
'TestStrategy'
|
||||||
|
Loading…
Reference in New Issue
Block a user