Merge branch 'develop' into timeframe
This commit is contained in:
commit
64881a94e2
@ -76,6 +76,15 @@
|
|||||||
"token": "your_telegram_token",
|
"token": "your_telegram_token",
|
||||||
"chat_id": "your_telegram_chat_id"
|
"chat_id": "your_telegram_chat_id"
|
||||||
},
|
},
|
||||||
|
"api_server": {
|
||||||
|
"enabled": false,
|
||||||
|
"listen_ip_address": "127.0.0.1",
|
||||||
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
|
"jwt_secret_key": "somethingrandom",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"forcebuy_enable": false,
|
"forcebuy_enable": false,
|
||||||
"internals": {
|
"internals": {
|
||||||
|
@ -81,6 +81,15 @@
|
|||||||
"token": "your_telegram_token",
|
"token": "your_telegram_token",
|
||||||
"chat_id": "your_telegram_chat_id"
|
"chat_id": "your_telegram_chat_id"
|
||||||
},
|
},
|
||||||
|
"api_server": {
|
||||||
|
"enabled": false,
|
||||||
|
"listen_ip_address": "127.0.0.1",
|
||||||
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
|
"jwt_secret_key": "somethingrandom",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"forcebuy_enable": false,
|
"forcebuy_enable": false,
|
||||||
"internals": {
|
"internals": {
|
||||||
|
@ -121,6 +121,7 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"listen_ip_address": "127.0.0.1",
|
"listen_ip_address": "127.0.0.1",
|
||||||
"listen_port": 8080,
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
"jwt_secret_key": "somethingrandom",
|
"jwt_secret_key": "somethingrandom",
|
||||||
"username": "freqtrader",
|
"username": "freqtrader",
|
||||||
"password": "SuperSecurePassword"
|
"password": "SuperSecurePassword"
|
||||||
|
@ -87,6 +87,15 @@
|
|||||||
"token": "your_telegram_token",
|
"token": "your_telegram_token",
|
||||||
"chat_id": "your_telegram_chat_id"
|
"chat_id": "your_telegram_chat_id"
|
||||||
},
|
},
|
||||||
|
"api_server": {
|
||||||
|
"enabled": false,
|
||||||
|
"listen_ip_address": "127.0.0.1",
|
||||||
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
|
"jwt_secret_key": "somethingrandom",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"forcebuy_enable": false,
|
"forcebuy_enable": false,
|
||||||
"internals": {
|
"internals": {
|
||||||
|
@ -53,8 +53,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
||||||
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||||
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float (as ratio)
|
| `stoploss` | **Required.** Value as ratio of the stoploss used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float (as ratio)
|
||||||
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Boolean
|
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Boolean
|
||||||
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float
|
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float
|
||||||
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
|
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
|
||||||
@ -103,6 +103,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
||||||
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
||||||
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
|
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
|
||||||
|
| `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors. <br>**Datatype:** Enum, either `info` or `error`. Defaults to `info`.
|
||||||
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
||||||
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
||||||
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string
|
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string
|
||||||
@ -217,7 +218,7 @@ To allow the bot to trade all the available `stake_currency` in your account (mi
|
|||||||
### Understand minimal_roi
|
### Understand minimal_roi
|
||||||
|
|
||||||
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
|
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
|
||||||
in minutes and the value is the minimum ROI in percent.
|
in minutes and the value is the minimum ROI as ratio.
|
||||||
See the example below:
|
See the example below:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -148,7 +148,6 @@ Edge module has following configuration options:
|
|||||||
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> **Datatype:** Integer
|
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> **Datatype:** Integer
|
||||||
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> **Datatype:** Integer
|
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> **Datatype:** Integer
|
||||||
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> **Datatype:** Float
|
|
||||||
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> **Datatype:** Float
|
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> **Datatype:** Float
|
||||||
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> **Datatype:** Float
|
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> **Datatype:** Float
|
||||||
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float
|
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float
|
||||||
|
@ -11,6 +11,7 @@ Sample configuration:
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"listen_ip_address": "127.0.0.1",
|
"listen_ip_address": "127.0.0.1",
|
||||||
"listen_port": 8080,
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
"jwt_secret_key": "somethingrandom",
|
"jwt_secret_key": "somethingrandom",
|
||||||
"username": "Freqtrader",
|
"username": "Freqtrader",
|
||||||
"password": "SuperSecret1!"
|
"password": "SuperSecret1!"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Stop Loss
|
# Stop Loss
|
||||||
|
|
||||||
The `stoploss` configuration parameter is loss in percentage that should trigger a sale.
|
The `stoploss` configuration parameter is loss as ratio that should trigger a sale.
|
||||||
For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional.
|
For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional.
|
||||||
|
|
||||||
Most of the strategy files already include the optimal `stoploss` value.
|
Most of the strategy files already include the optimal `stoploss` value.
|
||||||
|
@ -60,7 +60,7 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if (config.get('edge', {}).get('enabled', False)
|
if (config.get('edge', {}).get('enabled', False)
|
||||||
and 'capital_available_percentage' in config.get('edge', {})):
|
and 'capital_available_percentage' in config.get('edge', {})):
|
||||||
logger.warning(
|
raise OperationalException(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
||||||
"'tradable_balance_ratio'. Please migrate your configuration to "
|
"'tradable_balance_ratio'. Please migrate your configuration to "
|
||||||
|
@ -221,6 +221,7 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'username': {'type': 'string'},
|
'username': {'type': 'string'},
|
||||||
'password': {'type': 'string'},
|
'password': {'type': 'string'},
|
||||||
|
'verbosity': {'type': 'string', 'enum': ['error', 'info']},
|
||||||
},
|
},
|
||||||
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
||||||
},
|
},
|
||||||
@ -286,7 +287,6 @@ CONF_SCHEMA = {
|
|||||||
'process_throttle_secs': {'type': 'integer', 'minimum': 600},
|
'process_throttle_secs': {'type': 'integer', 'minimum': 600},
|
||||||
'calculate_since_number_of_days': {'type': 'integer'},
|
'calculate_since_number_of_days': {'type': 'integer'},
|
||||||
'allowed_risk': {'type': 'number'},
|
'allowed_risk': {'type': 'number'},
|
||||||
'capital_available_percentage': {'type': 'number'},
|
|
||||||
'stoploss_range_min': {'type': 'number'},
|
'stoploss_range_min': {'type': 'number'},
|
||||||
'stoploss_range_max': {'type': 'number'},
|
'stoploss_range_max': {'type': 'number'},
|
||||||
'stoploss_range_step': {'type': 'number'},
|
'stoploss_range_step': {'type': 'number'},
|
||||||
|
@ -57,9 +57,7 @@ class Edge:
|
|||||||
if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT:
|
if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT:
|
||||||
raise OperationalException('Edge works only with unlimited stake amount')
|
raise OperationalException('Edge works only with unlimited stake amount')
|
||||||
|
|
||||||
# Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future.
|
self._capital_ratio: float = self.config['tradable_balance_ratio']
|
||||||
self._capital_percentage: float = self.edge_config.get(
|
|
||||||
'capital_available_percentage', self.config['tradable_balance_ratio'])
|
|
||||||
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
|
||||||
@ -157,7 +155,7 @@ class Edge:
|
|||||||
def stake_amount(self, pair: str, free_capital: float,
|
def stake_amount(self, pair: str, free_capital: float,
|
||||||
total_capital: float, capital_in_trade: float) -> float:
|
total_capital: float, capital_in_trade: float) -> float:
|
||||||
stoploss = self.stoploss(pair)
|
stoploss = self.stoploss(pair)
|
||||||
available_capital = (total_capital + capital_in_trade) * self._capital_percentage
|
available_capital = (total_capital + capital_in_trade) * self._capital_ratio
|
||||||
allowed_capital_at_risk = available_capital * self._allowed_risk
|
allowed_capital_at_risk = available_capital * self._allowed_risk
|
||||||
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
||||||
position_size = min(max_position_size, free_capital)
|
position_size = min(max_position_size, free_capital)
|
||||||
|
@ -676,6 +676,8 @@ class FreqtradeBot:
|
|||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
else:
|
else:
|
||||||
rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']]
|
rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']]
|
||||||
|
if rate is None:
|
||||||
|
raise PricingError(f"Sell-Rate for {pair} was empty.")
|
||||||
self._sell_rate_cache[pair] = rate
|
self._sell_rate_cache[pair] = rate
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
@ -719,6 +721,9 @@ class FreqtradeBot:
|
|||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: "
|
logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: "
|
||||||
f"{sell_rate:0.8f}")
|
f"{sell_rate:0.8f}")
|
||||||
|
# Assign sell-rate to cache - otherwise sell-rate is never updated in the cache,
|
||||||
|
# resulting in outdated RPC messages
|
||||||
|
self._sell_rate_cache[trade.pair] = sell_rate
|
||||||
|
|
||||||
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
||||||
return True
|
return True
|
||||||
|
@ -11,7 +11,7 @@ from freqtrade.exceptions import OperationalException
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _set_loggers(verbosity: int = 0) -> None:
|
def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None:
|
||||||
"""
|
"""
|
||||||
Set the logging level for third party libraries
|
Set the logging level for third party libraries
|
||||||
:return: None
|
:return: None
|
||||||
@ -28,6 +28,10 @@ def _set_loggers(verbosity: int = 0) -> None:
|
|||||||
)
|
)
|
||||||
logging.getLogger('telegram').setLevel(logging.INFO)
|
logging.getLogger('telegram').setLevel(logging.INFO)
|
||||||
|
|
||||||
|
logging.getLogger('werkzeug').setLevel(
|
||||||
|
logging.ERROR if api_verbosity == 'error' else logging.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(config: Dict[str, Any]) -> None:
|
def setup_logging(config: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
@ -77,5 +81,5 @@ def setup_logging(config: Dict[str, Any]) -> None:
|
|||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
handlers=log_handlers
|
handlers=log_handlers
|
||||||
)
|
)
|
||||||
_set_loggers(verbosity)
|
_set_loggers(verbosity, config.get('api_server', {}).get('verbosity', 'info'))
|
||||||
logger.info('Verbosity set to %s', verbosity)
|
logger.info('Verbosity set to %s', verbosity)
|
||||||
|
@ -18,7 +18,8 @@ from freqtrade.data.converter import trim_dataframe
|
|||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
from freqtrade.optimize.optimize_reports import (show_backtest_results,
|
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
|
||||||
|
show_backtest_results,
|
||||||
store_backtest_result)
|
store_backtest_result)
|
||||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
@ -411,4 +412,5 @@ class Backtesting:
|
|||||||
if self.config.get('export', False):
|
if self.config.get('export', False):
|
||||||
store_backtest_result(self.config['exportfilename'], all_results)
|
store_backtest_result(self.config['exportfilename'], all_results)
|
||||||
# Show backtest results
|
# Show backtest results
|
||||||
show_backtest_results(self.config, data, all_results)
|
stats = generate_backtest_stats(self.config, data, all_results)
|
||||||
|
show_backtest_results(self.config, stats)
|
||||||
|
@ -18,10 +18,7 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame
|
|||||||
:param all_results: Dict of Dataframes, one results dataframe per strategy
|
:param all_results: Dict of Dataframes, one results dataframe per strategy
|
||||||
"""
|
"""
|
||||||
for strategy, results in all_results.items():
|
for strategy, results in all_results.items():
|
||||||
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
|
records = backtest_result_to_list(results)
|
||||||
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
|
|
||||||
t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value)
|
|
||||||
for index, t in results.iterrows()]
|
|
||||||
|
|
||||||
if records:
|
if records:
|
||||||
filename = recordfilename
|
filename = recordfilename
|
||||||
@ -34,6 +31,18 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame
|
|||||||
file_dump_json(filename, records)
|
file_dump_json(filename, records)
|
||||||
|
|
||||||
|
|
||||||
|
def backtest_result_to_list(results: DataFrame) -> List[List]:
|
||||||
|
"""
|
||||||
|
Converts a list of Backtest-results to list
|
||||||
|
:param results: Dataframe containing results for one strategy
|
||||||
|
:return: List of Lists containing the trades
|
||||||
|
"""
|
||||||
|
return [[t.pair, t.profit_percent, t.open_time.timestamp(),
|
||||||
|
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
|
||||||
|
t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value]
|
||||||
|
for index, t in results.iterrows()]
|
||||||
|
|
||||||
|
|
||||||
def _get_line_floatfmt() -> List[str]:
|
def _get_line_floatfmt() -> List[str]:
|
||||||
"""
|
"""
|
||||||
Generate floatformat (goes in line with _generate_result_line())
|
Generate floatformat (goes in line with _generate_result_line())
|
||||||
@ -246,12 +255,13 @@ def generate_edge_table(results: dict) -> str:
|
|||||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
|
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
|
def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame],
|
||||||
all_results: Dict[str, DataFrame]):
|
all_results: Dict[str, DataFrame]):
|
||||||
stake_currency = config['stake_currency']
|
stake_currency = config['stake_currency']
|
||||||
max_open_trades = config['max_open_trades']
|
max_open_trades = config['max_open_trades']
|
||||||
|
result: Dict[str, Any] = {'strategy': {}}
|
||||||
for strategy, results in all_results.items():
|
for strategy, results in all_results.items():
|
||||||
|
|
||||||
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
||||||
max_open_trades=max_open_trades,
|
max_open_trades=max_open_trades,
|
||||||
results=results, skip_nan=False)
|
results=results, skip_nan=False)
|
||||||
@ -261,21 +271,43 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
|
|||||||
max_open_trades=max_open_trades,
|
max_open_trades=max_open_trades,
|
||||||
results=results.loc[results['open_at_end']],
|
results=results.loc[results['open_at_end']],
|
||||||
skip_nan=True)
|
skip_nan=True)
|
||||||
|
strat_stats = {
|
||||||
|
'trades': backtest_result_to_list(results),
|
||||||
|
'results_per_pair': pair_results,
|
||||||
|
'sell_reason_summary': sell_reason_stats,
|
||||||
|
'left_open_trades': left_open_results,
|
||||||
|
}
|
||||||
|
result['strategy'][strategy] = strat_stats
|
||||||
|
|
||||||
|
strategy_results = generate_strategy_metrics(stake_currency=stake_currency,
|
||||||
|
max_open_trades=max_open_trades,
|
||||||
|
all_results=all_results)
|
||||||
|
|
||||||
|
result['strategy_comparison'] = strategy_results
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def show_backtest_results(config: Dict, backtest_stats: Dict):
|
||||||
|
stake_currency = config['stake_currency']
|
||||||
|
|
||||||
|
for strategy, results in backtest_stats['strategy'].items():
|
||||||
|
|
||||||
# Print results
|
# Print results
|
||||||
print(f"Result for strategy {strategy}")
|
print(f"Result for strategy {strategy}")
|
||||||
table = generate_text_table(pair_results, stake_currency=stake_currency)
|
table = generate_text_table(results['results_per_pair'], stake_currency=stake_currency)
|
||||||
if isinstance(table, str):
|
if isinstance(table, str):
|
||||||
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
table = generate_text_table_sell_reason(sell_reason_stats=sell_reason_stats,
|
table = generate_text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'],
|
||||||
stake_currency=stake_currency,
|
stake_currency=stake_currency,
|
||||||
)
|
)
|
||||||
if isinstance(table, str):
|
if isinstance(table, str):
|
||||||
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
|
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
table = generate_text_table(left_open_results, stake_currency=stake_currency)
|
table = generate_text_table(results['left_open_trades'], stake_currency=stake_currency)
|
||||||
if isinstance(table, str):
|
if isinstance(table, str):
|
||||||
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
@ -283,13 +315,10 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
|
|||||||
print('=' * len(table.splitlines()[0]))
|
print('=' * len(table.splitlines()[0]))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if len(all_results) > 1:
|
if len(backtest_stats['strategy']) > 1:
|
||||||
# Print Strategy summary table
|
# Print Strategy summary table
|
||||||
strategy_results = generate_strategy_metrics(stake_currency=stake_currency,
|
|
||||||
max_open_trades=max_open_trades,
|
|
||||||
all_results=all_results)
|
|
||||||
|
|
||||||
table = generate_text_table_strategy(strategy_results, stake_currency)
|
table = generate_text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
|
||||||
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
print('=' * len(table.splitlines()[0]))
|
print('=' * len(table.splitlines()[0]))
|
||||||
|
@ -108,7 +108,7 @@ def check_migrate(engine) -> None:
|
|||||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||||
strategy = get_column_def(cols, 'strategy', 'null')
|
strategy = get_column_def(cols, 'strategy', 'null')
|
||||||
# If ticker-interval existed use that, else null.
|
# If ticker-interval existed use that, else null.
|
||||||
if has_column(cols, 'ticker_interval'):
|
if has_column(cols, '):
|
||||||
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
||||||
else:
|
else:
|
||||||
timeframe = get_column_def(cols, 'timeframe', 'null')
|
timeframe = get_column_def(cols, 'timeframe', 'null')
|
||||||
@ -254,48 +254,57 @@ class Trade(_DECL_BASE):
|
|||||||
'trade_id': self.id,
|
'trade_id': self.id,
|
||||||
'pair': self.pair,
|
'pair': self.pair,
|
||||||
'is_open': self.is_open,
|
'is_open': self.is_open,
|
||||||
|
'exchange': self.exchange,
|
||||||
|
'amount': round(self.amount, 8),
|
||||||
|
'stake_amount': round(self.stake_amount, 8),
|
||||||
|
'strategy': self.strategy,
|
||||||
|
'timeframe': self.timeframe,
|
||||||
|
|
||||||
'fee_open': self.fee_open,
|
'fee_open': self.fee_open,
|
||||||
'fee_open_cost': self.fee_open_cost,
|
'fee_open_cost': self.fee_open_cost,
|
||||||
'fee_open_currency': self.fee_open_currency,
|
'fee_open_currency': self.fee_open_currency,
|
||||||
'fee_close': self.fee_close,
|
'fee_close': self.fee_close,
|
||||||
'fee_close_cost': self.fee_close_cost,
|
'fee_close_cost': self.fee_close_cost,
|
||||||
'fee_close_currency': self.fee_close_currency,
|
'fee_close_currency': self.fee_close_currency,
|
||||||
|
|
||||||
'open_date_hum': arrow.get(self.open_date).humanize(),
|
'open_date_hum': arrow.get(self.open_date).humanize(),
|
||||||
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'open_timestamp': int(self.open_date.timestamp() * 1000),
|
'open_timestamp': int(self.open_date.timestamp() * 1000),
|
||||||
|
'open_rate': self.open_rate,
|
||||||
|
'open_rate_requested': self.open_rate_requested,
|
||||||
|
'open_trade_price': self.open_trade_price,
|
||||||
|
|
||||||
'close_date_hum': (arrow.get(self.close_date).humanize()
|
'close_date_hum': (arrow.get(self.close_date).humanize()
|
||||||
if self.close_date else None),
|
if self.close_date else None),
|
||||||
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
|
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
if self.close_date else None),
|
if self.close_date else None),
|
||||||
'close_timestamp': int(self.close_date.timestamp() * 1000) if self.close_date else None,
|
'close_timestamp': int(self.close_date.timestamp() * 1000) if self.close_date else None,
|
||||||
'open_rate': self.open_rate,
|
|
||||||
'open_rate_requested': self.open_rate_requested,
|
|
||||||
'open_trade_price': self.open_trade_price,
|
|
||||||
'close_rate': self.close_rate,
|
'close_rate': self.close_rate,
|
||||||
'close_rate_requested': self.close_rate_requested,
|
'close_rate_requested': self.close_rate_requested,
|
||||||
'amount': round(self.amount, 8),
|
|
||||||
'stake_amount': round(self.stake_amount, 8),
|
|
||||||
'close_profit': self.close_profit,
|
'close_profit': self.close_profit,
|
||||||
'close_profit_abs': self.close_profit_abs,
|
'close_profit_abs': self.close_profit_abs,
|
||||||
|
|
||||||
'sell_reason': self.sell_reason,
|
'sell_reason': self.sell_reason,
|
||||||
'sell_order_status': self.sell_order_status,
|
'sell_order_status': self.sell_order_status,
|
||||||
'stop_loss': self.stop_loss,
|
'stop_loss': self.stop_loss, # Deprecated - should not be used
|
||||||
|
'stop_loss_abs': self.stop_loss,
|
||||||
|
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
||||||
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
||||||
'stoploss_order_id': self.stoploss_order_id,
|
'stoploss_order_id': self.stoploss_order_id,
|
||||||
'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S")
|
'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
if self.stoploss_last_update else None),
|
if self.stoploss_last_update else None),
|
||||||
'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000)
|
'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000)
|
||||||
if self.stoploss_last_update else None),
|
if self.stoploss_last_update else None),
|
||||||
'initial_stop_loss': self.initial_stop_loss,
|
'initial_stop_loss': self.initial_stop_loss, # Deprecated - should not be used
|
||||||
|
'initial_stop_loss_abs': self.initial_stop_loss,
|
||||||
|
'initial_stop_loss_ratio': (self.initial_stop_loss_pct
|
||||||
|
if self.initial_stop_loss_pct else None),
|
||||||
'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100
|
'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100
|
||||||
if self.initial_stop_loss_pct else None),
|
if self.initial_stop_loss_pct else None),
|
||||||
'min_rate': self.min_rate,
|
'min_rate': self.min_rate,
|
||||||
'max_rate': self.max_rate,
|
'max_rate': self.max_rate,
|
||||||
'strategy': self.strategy,
|
|
||||||
'ticker_interval': self.timeframe,
|
|
||||||
'timeframe': self.timeframe,
|
|
||||||
'open_order_id': self.open_order_id,
|
'open_order_id': self.open_order_id,
|
||||||
'exchange': self.exchange,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def adjust_min_max_rates(self, current_price: float) -> None:
|
def adjust_min_max_rates(self, current_price: float) -> None:
|
||||||
|
@ -360,7 +360,6 @@ class ApiServer(RPC):
|
|||||||
Returns a cumulative profit statistics
|
Returns a cumulative profit statistics
|
||||||
:return: stats
|
:return: stats
|
||||||
"""
|
"""
|
||||||
logger.info("LocalRPC - Profit Command Called")
|
|
||||||
|
|
||||||
stats = self._rpc_trade_statistics(self._config['stake_currency'],
|
stats = self._rpc_trade_statistics(self._config['stake_currency'],
|
||||||
self._config.get('fiat_display_currency')
|
self._config.get('fiat_display_currency')
|
||||||
@ -377,8 +376,6 @@ class ApiServer(RPC):
|
|||||||
Returns a cumulative performance statistics
|
Returns a cumulative performance statistics
|
||||||
:return: stats
|
:return: stats
|
||||||
"""
|
"""
|
||||||
logger.info("LocalRPC - performance Command Called")
|
|
||||||
|
|
||||||
stats = self._rpc_performance()
|
stats = self._rpc_performance()
|
||||||
|
|
||||||
return self.rest_dump(stats)
|
return self.rest_dump(stats)
|
||||||
|
@ -532,16 +532,26 @@ class RPC:
|
|||||||
|
|
||||||
def _rpc_blacklist(self, add: List[str] = None) -> Dict:
|
def _rpc_blacklist(self, add: List[str] = None) -> Dict:
|
||||||
""" Returns the currently active blacklist"""
|
""" Returns the currently active blacklist"""
|
||||||
|
errors = {}
|
||||||
if add:
|
if add:
|
||||||
stake_currency = self._freqtrade.config.get('stake_currency')
|
stake_currency = self._freqtrade.config.get('stake_currency')
|
||||||
for pair in add:
|
for pair in add:
|
||||||
if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency
|
if self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
|
||||||
and pair not in self._freqtrade.pairlists.blacklist):
|
if pair not in self._freqtrade.pairlists.blacklist:
|
||||||
self._freqtrade.pairlists.blacklist.append(pair)
|
self._freqtrade.pairlists.blacklist.append(pair)
|
||||||
|
else:
|
||||||
|
errors[pair] = {
|
||||||
|
'error_msg': f'Pair {pair} already in pairlist.'}
|
||||||
|
|
||||||
|
else:
|
||||||
|
errors[pair] = {
|
||||||
|
'error_msg': f"Pair {pair} does not match stake currency."
|
||||||
|
}
|
||||||
|
|
||||||
res = {'method': self._freqtrade.pairlists.name_list,
|
res = {'method': self._freqtrade.pairlists.name_list,
|
||||||
'length': len(self._freqtrade.pairlists.blacklist),
|
'length': len(self._freqtrade.pairlists.blacklist),
|
||||||
'blacklist': self._freqtrade.pairlists.blacklist,
|
'blacklist': self._freqtrade.pairlists.blacklist,
|
||||||
|
'errors': errors,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -539,6 +539,11 @@ class Telegram(RPC):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
blacklist = self._rpc_blacklist(context.args)
|
blacklist = self._rpc_blacklist(context.args)
|
||||||
|
errmsgs = []
|
||||||
|
for pair, error in blacklist['errors'].items():
|
||||||
|
errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
|
||||||
|
if errmsgs:
|
||||||
|
self._send_msg('\n'.join(errmsgs))
|
||||||
|
|
||||||
message = f"Blacklist contains {blacklist['length']} pairs\n"
|
message = f"Blacklist contains {blacklist['length']} pairs\n"
|
||||||
message += f"`{', '.join(blacklist['blacklist'])}`"
|
message += f"`{', '.join(blacklist['blacklist'])}`"
|
||||||
|
@ -53,6 +53,15 @@
|
|||||||
"token": "{{ telegram_token }}",
|
"token": "{{ telegram_token }}",
|
||||||
"chat_id": "{{ telegram_chat_id }}"
|
"chat_id": "{{ telegram_chat_id }}"
|
||||||
},
|
},
|
||||||
|
"api_server": {
|
||||||
|
"enabled": false,
|
||||||
|
"listen_ip_address": "127.0.0.1",
|
||||||
|
"listen_port": 8080,
|
||||||
|
"verbosity": "info",
|
||||||
|
"jwt_secret_key": "somethingrandom",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"forcebuy_enable": false,
|
"forcebuy_enable": false,
|
||||||
"internals": {
|
"internals": {
|
||||||
|
@ -92,7 +92,6 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"process_throttle_secs": 3600,
|
"process_throttle_secs": 3600,
|
||||||
"calculate_since_number_of_days": 7,
|
"calculate_since_number_of_days": 7,
|
||||||
"capital_available_percentage": 0.5,
|
|
||||||
"allowed_risk": 0.01,
|
"allowed_risk": 0.01,
|
||||||
"stoploss_range_min": -0.01,
|
"stoploss_range_min": -0.01,
|
||||||
"stoploss_range_max": -0.1,
|
"stoploss_range_max": -0.1,
|
||||||
|
@ -333,6 +333,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
|||||||
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
||||||
|
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
|
||||||
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
|
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
|
||||||
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
@ -612,8 +613,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
|||||||
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||||
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
||||||
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock())
|
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
|
||||||
|
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
|
||||||
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
@ -82,12 +82,16 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'current_profit': -0.00408133,
|
'current_profit': -0.00408133,
|
||||||
'current_profit_pct': -0.41,
|
'current_profit_pct': -0.41,
|
||||||
'stop_loss': 0.0,
|
'stop_loss': 0.0,
|
||||||
|
'stop_loss_abs': 0.0,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss': 0.0,
|
'initial_stop_loss': 0.0,
|
||||||
|
'initial_stop_loss_abs': 0.0,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'open_order': '(limit buy rem=0.00000000)',
|
'open_order': '(limit buy rem=0.00000000)',
|
||||||
'exchange': 'bittrex',
|
'exchange': 'bittrex',
|
||||||
|
|
||||||
@ -137,12 +141,16 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'current_profit': ANY,
|
'current_profit': ANY,
|
||||||
'current_profit_pct': ANY,
|
'current_profit_pct': ANY,
|
||||||
'stop_loss': 0.0,
|
'stop_loss': 0.0,
|
||||||
|
'stop_loss_abs': 0.0,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss': 0.0,
|
'initial_stop_loss': 0.0,
|
||||||
|
'initial_stop_loss_abs': 0.0,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'open_order': '(limit buy rem=0.00000000)',
|
'open_order': '(limit buy rem=0.00000000)',
|
||||||
'exchange': 'bittrex',
|
'exchange': 'bittrex',
|
||||||
} == results[0]
|
} == results[0]
|
||||||
@ -850,6 +858,20 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
|
|||||||
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
|
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
|
||||||
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
|
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
|
||||||
|
|
||||||
|
ret = rpc._rpc_blacklist(["ETH/BTC"])
|
||||||
|
assert 'errors' in ret
|
||||||
|
assert isinstance(ret['errors'], dict)
|
||||||
|
assert ret['errors']['ETH/BTC']['error_msg'] == 'Pair ETH/BTC already in pairlist.'
|
||||||
|
|
||||||
|
ret = rpc._rpc_blacklist(["ETH/ETH"])
|
||||||
|
assert 'StaticPairList' in ret['method']
|
||||||
|
assert len(ret['blacklist']) == 3
|
||||||
|
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
|
||||||
|
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
|
||||||
|
assert 'errors' in ret
|
||||||
|
assert isinstance(ret['errors'], dict)
|
||||||
|
assert ret['errors']['ETH/ETH']['error_msg'] == 'Pair ETH/ETH does not match stake currency.'
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
@ -510,8 +510,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
|||||||
'current_profit': -0.00408133,
|
'current_profit': -0.00408133,
|
||||||
'current_profit_pct': -0.41,
|
'current_profit_pct': -0.41,
|
||||||
'current_rate': 1.099e-05,
|
'current_rate': 1.099e-05,
|
||||||
'initial_stop_loss': 0.0,
|
|
||||||
'initial_stop_loss_pct': None,
|
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'open_date_hum': 'just now',
|
'open_date_hum': 'just now',
|
||||||
'open_timestamp': ANY,
|
'open_timestamp': ANY,
|
||||||
@ -520,10 +518,16 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
|||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'stop_loss': 0.0,
|
'stop_loss': 0.0,
|
||||||
|
'stop_loss_abs': 0.0,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
|
'initial_stop_loss': 0.0,
|
||||||
|
'initial_stop_loss_abs': 0.0,
|
||||||
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'close_rate_requested': None,
|
'close_rate_requested': None,
|
||||||
'current_rate': 1.099e-05,
|
'current_rate': 1.099e-05,
|
||||||
@ -564,7 +568,9 @@ def test_api_blacklist(botclient, mocker):
|
|||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"],
|
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"],
|
||||||
"length": 2,
|
"length": 2,
|
||||||
"method": ["StaticPairList"]}
|
"method": ["StaticPairList"],
|
||||||
|
"errors": {},
|
||||||
|
}
|
||||||
|
|
||||||
# Add ETH/BTC to blacklist
|
# Add ETH/BTC to blacklist
|
||||||
rc = client_post(client, f"{BASE_URI}/blacklist",
|
rc = client_post(client, f"{BASE_URI}/blacklist",
|
||||||
@ -572,7 +578,9 @@ def test_api_blacklist(botclient, mocker):
|
|||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
|
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
|
||||||
"length": 3,
|
"length": 3,
|
||||||
"method": ["StaticPairList"]}
|
"method": ["StaticPairList"],
|
||||||
|
"errors": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_api_whitelist(botclient):
|
def test_api_whitelist(botclient):
|
||||||
@ -623,12 +631,11 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
data='{"pair": "ETH/BTC"}')
|
data='{"pair": "ETH/BTC"}')
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json == {'amount': 1,
|
assert rc.json == {'amount': 1,
|
||||||
|
'trade_id': None,
|
||||||
'close_date': None,
|
'close_date': None,
|
||||||
'close_date_hum': None,
|
'close_date_hum': None,
|
||||||
'close_timestamp': None,
|
'close_timestamp': None,
|
||||||
'close_rate': 0.265441,
|
'close_rate': 0.265441,
|
||||||
'initial_stop_loss': None,
|
|
||||||
'initial_stop_loss_pct': None,
|
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'open_date_hum': 'just now',
|
'open_date_hum': 'just now',
|
||||||
'open_timestamp': ANY,
|
'open_timestamp': ANY,
|
||||||
@ -636,11 +643,16 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
'pair': 'ETH/ETH',
|
'pair': 'ETH/ETH',
|
||||||
'stake_amount': 1,
|
'stake_amount': 1,
|
||||||
'stop_loss': None,
|
'stop_loss': None,
|
||||||
|
'stop_loss_abs': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'trade_id': None,
|
'initial_stop_loss': None,
|
||||||
|
'initial_stop_loss_abs': None,
|
||||||
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
'close_rate_requested': None,
|
'close_rate_requested': None,
|
||||||
|
@ -1087,6 +1087,18 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
|
|||||||
in msg_mock.call_args_list[0][0][0])
|
in msg_mock.call_args_list[0][0][0])
|
||||||
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
|
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
|
||||||
|
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
context = MagicMock()
|
||||||
|
context.args = ["ETH/ETH"]
|
||||||
|
telegram._blacklist(update=update, context=context)
|
||||||
|
assert msg_mock.call_count == 2
|
||||||
|
assert ("Error adding `ETH/ETH` to blacklist: `Pair ETH/ETH does not match stake currency.`"
|
||||||
|
in msg_mock.call_args_list[0][0][0])
|
||||||
|
|
||||||
|
assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`"
|
||||||
|
in msg_mock.call_args_list[1][0][0])
|
||||||
|
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
|
||||||
|
|
||||||
|
|
||||||
def test_edge_disabled(default_conf, update, mocker) -> None:
|
def test_edge_disabled(default_conf, update, mocker) -> None:
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
|
@ -654,12 +654,14 @@ def test_set_loggers() -> None:
|
|||||||
assert logging.getLogger('requests').level is logging.DEBUG
|
assert logging.getLogger('requests').level is logging.DEBUG
|
||||||
assert logging.getLogger('ccxt.base.exchange').level is logging.INFO
|
assert logging.getLogger('ccxt.base.exchange').level is logging.INFO
|
||||||
assert logging.getLogger('telegram').level is logging.INFO
|
assert logging.getLogger('telegram').level is logging.INFO
|
||||||
|
assert logging.getLogger('werkzeug').level is logging.INFO
|
||||||
|
|
||||||
_set_loggers(verbosity=3)
|
_set_loggers(verbosity=3, api_verbosity='error')
|
||||||
|
|
||||||
assert logging.getLogger('requests').level is logging.DEBUG
|
assert logging.getLogger('requests').level is logging.DEBUG
|
||||||
assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG
|
assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG
|
||||||
assert logging.getLogger('telegram').level is logging.INFO
|
assert logging.getLogger('telegram').level is logging.INFO
|
||||||
|
assert logging.getLogger('werkzeug').level is logging.ERROR
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||||
@ -1048,8 +1050,9 @@ def test_process_deprecated_setting_edge(mocker, edge_conf, caplog):
|
|||||||
'capital_available_percentage': 0.5,
|
'capital_available_percentage': 0.5,
|
||||||
}})
|
}})
|
||||||
|
|
||||||
process_temporary_deprecated_settings(edge_conf)
|
with pytest.raises(OperationalException,
|
||||||
assert log_has_re(r"DEPRECATED.*Using 'edge.capital_available_percentage'*", caplog)
|
match=r"DEPRECATED.*Using 'edge.capital_available_percentage'*"):
|
||||||
|
process_temporary_deprecated_settings(edge_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_check_conflicting_settings(mocker, default_conf, caplog):
|
def test_check_conflicting_settings(mocker, default_conf, caplog):
|
||||||
|
@ -3931,6 +3931,28 @@ def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
|
|||||||
assert log_has("Sell Price at location from orderbook could not be determined.", caplog)
|
assert log_has("Sell Price at location from orderbook could not be determined.", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_sell_rate_exception(default_conf, mocker, caplog):
|
||||||
|
# Ticker on one side can be empty in certain circumstances.
|
||||||
|
default_conf['ask_strategy']['price_side'] = 'ask'
|
||||||
|
pair = "ETH/BTC"
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
|
return_value={'ask': None, 'bid': 0.12})
|
||||||
|
ft = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||||
|
ft.get_sell_rate(pair, True)
|
||||||
|
|
||||||
|
ft.config['ask_strategy']['price_side'] = 'bid'
|
||||||
|
assert ft.get_sell_rate(pair, True) == 0.12
|
||||||
|
# Reverse sides
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
|
return_value={'ask': 0.13, 'bid': None})
|
||||||
|
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||||
|
ft.get_sell_rate(pair, True)
|
||||||
|
|
||||||
|
ft.config['ask_strategy']['price_side'] = 'ask'
|
||||||
|
assert ft.get_sell_rate(pair, True) == 0.13
|
||||||
|
|
||||||
|
|
||||||
def test_startup_state(default_conf, mocker):
|
def test_startup_state(default_conf, mocker):
|
||||||
default_conf['pairlist'] = {'method': 'VolumePairList',
|
default_conf['pairlist'] = {'method': 'VolumePairList',
|
||||||
'config': {'number_assets': 20}
|
'config': {'number_assets': 20}
|
||||||
|
@ -763,12 +763,16 @@ def test_to_json(default_conf, fee):
|
|||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
'sell_order_status': None,
|
'sell_order_status': None,
|
||||||
'stop_loss': None,
|
'stop_loss': None,
|
||||||
|
'stop_loss_abs': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss': None,
|
'initial_stop_loss': None,
|
||||||
|
'initial_stop_loss_abs': None,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'min_rate': None,
|
'min_rate': None,
|
||||||
'max_rate': None,
|
'max_rate': None,
|
||||||
'strategy': None,
|
'strategy': None,
|
||||||
@ -806,12 +810,16 @@ def test_to_json(default_conf, fee):
|
|||||||
'amount': 100.0,
|
'amount': 100.0,
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'stop_loss': None,
|
'stop_loss': None,
|
||||||
|
'stop_loss_abs': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss': None,
|
'initial_stop_loss': None,
|
||||||
|
'initial_stop_loss_abs': None,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
'close_rate_requested': None,
|
'close_rate_requested': None,
|
||||||
|
Loading…
Reference in New Issue
Block a user