Merge branch 'develop' into timeframe
This commit is contained in:
@@ -60,7 +60,7 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
|
||||
if (config.get('edge', {}).get('enabled', False)
|
||||
and 'capital_available_percentage' in config.get('edge', {})):
|
||||
logger.warning(
|
||||
raise OperationalException(
|
||||
"DEPRECATED: "
|
||||
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
||||
"'tradable_balance_ratio'. Please migrate your configuration to "
|
||||
|
@@ -221,6 +221,7 @@ CONF_SCHEMA = {
|
||||
},
|
||||
'username': {'type': 'string'},
|
||||
'password': {'type': 'string'},
|
||||
'verbosity': {'type': 'string', 'enum': ['error', 'info']},
|
||||
},
|
||||
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
||||
},
|
||||
@@ -286,7 +287,6 @@ CONF_SCHEMA = {
|
||||
'process_throttle_secs': {'type': 'integer', 'minimum': 600},
|
||||
'calculate_since_number_of_days': {'type': 'integer'},
|
||||
'allowed_risk': {'type': 'number'},
|
||||
'capital_available_percentage': {'type': 'number'},
|
||||
'stoploss_range_min': {'type': 'number'},
|
||||
'stoploss_range_max': {'type': 'number'},
|
||||
'stoploss_range_step': {'type': 'number'},
|
||||
|
@@ -57,9 +57,7 @@ class Edge:
|
||||
if self.config['stake_amount'] != 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_percentage: float = self.edge_config.get(
|
||||
'capital_available_percentage', self.config['tradable_balance_ratio'])
|
||||
self._capital_ratio: float = self.config['tradable_balance_ratio']
|
||||
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._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,
|
||||
total_capital: float, capital_in_trade: float) -> float:
|
||||
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
|
||||
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
||||
position_size = min(max_position_size, free_capital)
|
||||
|
@@ -676,6 +676,8 @@ class FreqtradeBot:
|
||||
raise PricingError from e
|
||||
else:
|
||||
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
|
||||
return rate
|
||||
|
||||
@@ -719,6 +721,9 @@ class FreqtradeBot:
|
||||
raise PricingError from e
|
||||
logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: "
|
||||
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):
|
||||
return True
|
||||
|
@@ -11,7 +11,7 @@ from freqtrade.exceptions import OperationalException
|
||||
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
|
||||
:return: None
|
||||
@@ -28,6 +28,10 @@ def _set_loggers(verbosity: int = 0) -> None:
|
||||
)
|
||||
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:
|
||||
"""
|
||||
@@ -77,5 +81,5 @@ def setup_logging(config: Dict[str, Any]) -> None:
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=log_handlers
|
||||
)
|
||||
_set_loggers(verbosity)
|
||||
_set_loggers(verbosity, config.get('api_server', {}).get('verbosity', 'info'))
|
||||
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.exceptions import OperationalException
|
||||
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)
|
||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||
from freqtrade.persistence import Trade
|
||||
@@ -411,4 +412,5 @@ class Backtesting:
|
||||
if self.config.get('export', False):
|
||||
store_backtest_result(self.config['exportfilename'], all_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
|
||||
"""
|
||||
for strategy, results in all_results.items():
|
||||
records = [(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()]
|
||||
records = backtest_result_to_list(results)
|
||||
|
||||
if records:
|
||||
filename = recordfilename
|
||||
@@ -34,6 +31,18 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame
|
||||
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]:
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
|
||||
all_results: Dict[str, DataFrame]):
|
||||
def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame],
|
||||
all_results: Dict[str, DataFrame]):
|
||||
stake_currency = config['stake_currency']
|
||||
max_open_trades = config['max_open_trades']
|
||||
|
||||
result: Dict[str, Any] = {'strategy': {}}
|
||||
for strategy, results in all_results.items():
|
||||
|
||||
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
|
||||
max_open_trades=max_open_trades,
|
||||
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,
|
||||
results=results.loc[results['open_at_end']],
|
||||
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(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):
|
||||
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
||||
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,
|
||||
)
|
||||
if isinstance(table, str):
|
||||
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||
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):
|
||||
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
||||
print(table)
|
||||
@@ -283,13 +315,10 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
|
||||
print('=' * len(table.splitlines()[0]))
|
||||
print()
|
||||
|
||||
if len(all_results) > 1:
|
||||
if len(backtest_stats['strategy']) > 1:
|
||||
# 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(table)
|
||||
print('=' * len(table.splitlines()[0]))
|
||||
|
@@ -108,7 +108,7 @@ def check_migrate(engine) -> None:
|
||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||
strategy = get_column_def(cols, 'strategy', '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')
|
||||
else:
|
||||
timeframe = get_column_def(cols, 'timeframe', 'null')
|
||||
@@ -254,48 +254,57 @@ class Trade(_DECL_BASE):
|
||||
'trade_id': self.id,
|
||||
'pair': self.pair,
|
||||
'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_cost': self.fee_open_cost,
|
||||
'fee_open_currency': self.fee_open_currency,
|
||||
'fee_close': self.fee_close,
|
||||
'fee_close_cost': self.fee_close_cost,
|
||||
'fee_close_currency': self.fee_close_currency,
|
||||
|
||||
'open_date_hum': arrow.get(self.open_date).humanize(),
|
||||
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'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()
|
||||
if self.close_date else None),
|
||||
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
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_requested': self.close_rate_requested,
|
||||
'amount': round(self.amount, 8),
|
||||
'stake_amount': round(self.stake_amount, 8),
|
||||
'close_profit': self.close_profit,
|
||||
'close_profit_abs': self.close_profit_abs,
|
||||
|
||||
'sell_reason': self.sell_reason,
|
||||
'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,
|
||||
'stoploss_order_id': self.stoploss_order_id,
|
||||
'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if self.stoploss_last_update else None),
|
||||
'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000)
|
||||
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
|
||||
if self.initial_stop_loss_pct else None),
|
||||
'min_rate': self.min_rate,
|
||||
'max_rate': self.max_rate,
|
||||
'strategy': self.strategy,
|
||||
'ticker_interval': self.timeframe,
|
||||
'timeframe': self.timeframe,
|
||||
|
||||
'open_order_id': self.open_order_id,
|
||||
'exchange': self.exchange,
|
||||
}
|
||||
|
||||
def adjust_min_max_rates(self, current_price: float) -> None:
|
||||
|
@@ -360,7 +360,6 @@ class ApiServer(RPC):
|
||||
Returns a cumulative profit statistics
|
||||
:return: stats
|
||||
"""
|
||||
logger.info("LocalRPC - Profit Command Called")
|
||||
|
||||
stats = self._rpc_trade_statistics(self._config['stake_currency'],
|
||||
self._config.get('fiat_display_currency')
|
||||
@@ -377,8 +376,6 @@ class ApiServer(RPC):
|
||||
Returns a cumulative performance statistics
|
||||
:return: stats
|
||||
"""
|
||||
logger.info("LocalRPC - performance Command Called")
|
||||
|
||||
stats = self._rpc_performance()
|
||||
|
||||
return self.rest_dump(stats)
|
||||
|
@@ -532,16 +532,26 @@ class RPC:
|
||||
|
||||
def _rpc_blacklist(self, add: List[str] = None) -> Dict:
|
||||
""" Returns the currently active blacklist"""
|
||||
errors = {}
|
||||
if add:
|
||||
stake_currency = self._freqtrade.config.get('stake_currency')
|
||||
for pair in add:
|
||||
if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency
|
||||
and pair not in self._freqtrade.pairlists.blacklist):
|
||||
self._freqtrade.pairlists.blacklist.append(pair)
|
||||
if self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
|
||||
if pair not in self._freqtrade.pairlists.blacklist:
|
||||
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,
|
||||
'length': len(self._freqtrade.pairlists.blacklist),
|
||||
'blacklist': self._freqtrade.pairlists.blacklist,
|
||||
'errors': errors,
|
||||
}
|
||||
return res
|
||||
|
||||
|
@@ -539,6 +539,11 @@ class Telegram(RPC):
|
||||
try:
|
||||
|
||||
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"`{', '.join(blacklist['blacklist'])}`"
|
||||
|
@@ -53,6 +53,15 @@
|
||||
"token": "{{ telegram_token }}",
|
||||
"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",
|
||||
"forcebuy_enable": false,
|
||||
"internals": {
|
||||
|
Reference in New Issue
Block a user