A few more formatting updates

This commit is contained in:
Matthias 2024-05-13 19:49:15 +02:00
parent 6a802f5624
commit 9291698561
20 changed files with 198 additions and 137 deletions

View File

@ -73,9 +73,9 @@ def ask_user_config() -> Dict[str, Any]:
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "unlimited", "default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"' "filter": lambda val: (
if val == UNLIMITED_STAKE_AMOUNT '"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
else val, ),
}, },
{ {
"type": "text", "type": "text",

View File

@ -340,9 +340,9 @@ AVAILABLE_CLI_OPTIONS = {
"hyperopt_loss": Arg( "hyperopt_loss": Arg(
"--hyperopt-loss", "--hyperopt-loss",
"--hyperoptloss", "--hyperoptloss",
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' help="Specify the class name of the hyperopt loss function class (IHyperOptLoss). "
'Different functions can generate completely different results, ' "Different functions can generate completely different results, "
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' "since the target for optimization is different. Built-in Hyperopt-loss-functions are: "
f'{", ".join(HYPEROPT_LOSS_BUILTIN)}', f'{", ".join(HYPEROPT_LOSS_BUILTIN)}',
metavar="NAME", metavar="NAME",
), ),

View File

@ -41,9 +41,11 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str
key = env_var.replace(prefix, "") key = env_var.replace(prefix, "")
for k in reversed(key.split("__")): for k in reversed(key.split("__")):
val = { val = {
k.lower(): _get_var_typed(val) k.lower(): (
if not isinstance(val, dict) and k not in no_convert _get_var_typed(val)
else val if not isinstance(val, dict) and k not in no_convert
else val
)
} }
relevant_vars = deep_merge_dicts(val, relevant_vars) relevant_vars = deep_merge_dicts(val, relevant_vars)
return relevant_vars return relevant_vars

View File

@ -262,7 +262,7 @@ def _download_pair_history(
logger.info( logger.info(
f'({process}) - Download history data for "{pair}", {timeframe}, ' f'({process}) - Download history data for "{pair}", {timeframe}, '
f'{candle_type} and store in {datadir}. ' f"{candle_type} and store in {datadir}. "
f'From {format_ms_time(since_ms) if since_ms else "start"} to ' f'From {format_ms_time(since_ms) if since_ms else "start"} to '
f'{format_ms_time(until_ms) if until_ms else "now"}' f'{format_ms_time(until_ms) if until_ms else "now"}'
) )
@ -280,9 +280,11 @@ def _download_pair_history(
new_data = exchange.get_historic_ohlcv( new_data = exchange.get_historic_ohlcv(
pair=pair, pair=pair,
timeframe=timeframe, timeframe=timeframe,
since_ms=since_ms since_ms=(
if since_ms since_ms
else int((datetime.now() - timedelta(days=new_pairs_days)).timestamp()) * 1000, if since_ms
else int((datetime.now() - timedelta(days=new_pairs_days)).timestamp()) * 1000
),
is_new_pair=data.empty, is_new_pair=data.empty,
candle_type=candle_type, candle_type=candle_type,
until_ms=until_ms if until_ms else None, until_ms=until_ms if until_ms else None,

View File

@ -33,17 +33,17 @@ def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
exchange = config.get("exchange", {}).get("name", "").lower() exchange = config.get("exchange", {}).get("name", "").lower()
if not exchange: if not exchange:
raise OperationalException( raise OperationalException(
f'This command requires a configured exchange. You should either use ' f"This command requires a configured exchange. You should either use "
f'`--exchange <exchange_name>` or specify a configuration file via `--config`.\n' f"`--exchange <exchange_name>` or specify a configuration file via `--config`.\n"
f'The following exchanges are available for Freqtrade: ' f"The following exchanges are available for Freqtrade: "
f'{", ".join(available_exchanges())}' f'{", ".join(available_exchanges())}'
) )
if not is_exchange_known_ccxt(exchange): if not is_exchange_known_ccxt(exchange):
raise OperationalException( raise OperationalException(
f'Exchange "{exchange}" is not known to the ccxt library ' f'Exchange "{exchange}" is not known to the ccxt library '
f'and therefore not available for the bot.\n' f"and therefore not available for the bot.\n"
f'The following exchanges are available for Freqtrade: ' f"The following exchanges are available for Freqtrade: "
f'{", ".join(available_exchanges())}' f'{", ".join(available_exchanges())}'
) )

View File

@ -1310,8 +1310,8 @@ class FreqtradeBot(LoggingMixin):
if should_exit.exit_flag: if should_exit.exit_flag:
exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None
logger.info( logger.info(
f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}' f"Exit for {trade.pair} detected. Reason: {should_exit.exit_type}"
f'{f" Tag: {exit_tag1}" if exit_tag1 is not None else ""}' f"{f' Tag: {exit_tag1}' if exit_tag1 is not None else ''}"
) )
exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag1) exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag1)
if exited: if exited:

View File

@ -230,9 +230,11 @@ class Hyperopt:
result["trailing"] = self.custom_hyperopt.generate_trailing_params(params) result["trailing"] = self.custom_hyperopt.generate_trailing_params(params)
if HyperoptTools.has_space(self.config, "trades"): if HyperoptTools.has_space(self.config, "trades"):
result["max_open_trades"] = { result["max_open_trades"] = {
"max_open_trades": self.backtesting.strategy.max_open_trades "max_open_trades": (
if self.backtesting.strategy.max_open_trades != float("inf") self.backtesting.strategy.max_open_trades
else -1 if self.backtesting.strategy.max_open_trades != float("inf")
else -1
)
} }
return result return result

View File

@ -457,11 +457,13 @@ class HyperoptTools:
lambda x: f"{x:,.2%}".rjust(7, " ") if not isna(x) else "--".rjust(7, " ") lambda x: f"{x:,.2%}".rjust(7, " ") if not isna(x) else "--".rjust(7, " ")
) )
trials["Avg duration"] = trials["Avg duration"].apply( trials["Avg duration"] = trials["Avg duration"].apply(
lambda x: f"{x:,.1f} m".rjust(7, " ") lambda x: (
if isinstance(x, float) f"{x:,.1f} m".rjust(7, " ")
else f"{x}" if isinstance(x, float)
if not isna(x) else f"{x}"
else "--".rjust(7, " ") if not isna(x)
else "--".rjust(7, " ")
)
) )
trials["Objective"] = trials["Objective"].apply( trials["Objective"] = trials["Objective"].apply(
lambda x: f"{x:,.5f}".rjust(8, " ") if x != 100000 else "N/A".rjust(8, " ") lambda x: f"{x:,.5f}".rjust(8, " ") if x != 100000 else "N/A".rjust(8, " ")
@ -470,28 +472,32 @@ class HyperoptTools:
stake_currency = config["stake_currency"] stake_currency = config["stake_currency"]
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply( trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
lambda x: "{} {}".format( lambda x: (
fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True), "{} {}".format(
( fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True),
f"({x['max_drawdown_account']:,.2%})" (
if has_account_drawdown f"({x['max_drawdown_account']:,.2%})"
else f"({x['max_drawdown']:,.2%})" if has_account_drawdown
).rjust(10, " "), else f"({x['max_drawdown']:,.2%})"
).rjust(25 + len(stake_currency)) ).rjust(10, " "),
if x["max_drawdown"] != 0.0 or x["max_drawdown_account"] != 0.0 ).rjust(25 + len(stake_currency))
else "--".rjust(25 + len(stake_currency)), if x["max_drawdown"] != 0.0 or x["max_drawdown_account"] != 0.0
else "--".rjust(25 + len(stake_currency))
),
axis=1, axis=1,
) )
trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown", "max_drawdown_account"]) trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown", "max_drawdown_account"])
trials["Profit"] = trials.apply( trials["Profit"] = trials.apply(
lambda x: "{} {}".format( lambda x: (
fmt_coin(x["Total profit"], stake_currency, keep_trailing_zeros=True), "{} {}".format(
f"({x['Profit']:,.2%})".rjust(10, " "), fmt_coin(x["Total profit"], stake_currency, keep_trailing_zeros=True),
).rjust(25 + len(stake_currency)) f"({x['Profit']:,.2%})".rjust(10, " "),
if x["Total profit"] != 0.0 ).rjust(25 + len(stake_currency))
else "--".rjust(25 + len(stake_currency)), if x["Total profit"] != 0.0
else "--".rjust(25 + len(stake_currency))
),
axis=1, axis=1,
) )
trials = trials.drop(columns=["Total profit"]) trials = trials.drop(columns=["Total profit"])

View File

@ -89,9 +89,11 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
floatfmt = _get_line_floatfmt(stake_currency) floatfmt = _get_line_floatfmt(stake_currency)
output = [ output = [
[ [
t["key"] (
if t.get("key") is not None and len(str(t["key"])) > 0 t["key"]
else t.get(fallback, "OTHER"), if t.get("key") is not None and len(str(t["key"])) > 0
else t.get(fallback, "OTHER")
),
t["trades"], t["trades"],
t["profit_mean_pct"], t["profit_mean_pct"],
t["profit_total_abs"], t["profit_total_abs"],
@ -218,9 +220,11 @@ def text_table_add_metrics(strat_results: Dict) -> str:
) )
drawdown_metrics.extend( drawdown_metrics.extend(
[ [
("Absolute Drawdown (Account)", f"{strat_results['max_drawdown_account']:.2%}") (
if "max_drawdown_account" in strat_results ("Absolute Drawdown (Account)", f"{strat_results['max_drawdown_account']:.2%}")
else ("Drawdown", f"{strat_results['max_drawdown']:.2%}"), if "max_drawdown_account" in strat_results
else ("Drawdown", f"{strat_results['max_drawdown']:.2%}")
),
( (
"Absolute Drawdown", "Absolute Drawdown",
fmt_coin(strat_results["max_drawdown_abs"], strat_results["stake_currency"]), fmt_coin(strat_results["max_drawdown_abs"], strat_results["stake_currency"]),
@ -279,9 +283,11 @@ def text_table_add_metrics(strat_results: Dict) -> str:
("Calmar", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A"), ("Calmar", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A"),
( (
"Profit factor", "Profit factor",
f'{strat_results["profit_factor"]:.2f}' (
if "profit_factor" in strat_results f'{strat_results["profit_factor"]:.2f}'
else "N/A", if "profit_factor" in strat_results
else "N/A"
),
), ),
( (
"Expectancy (Ratio)", "Expectancy (Ratio)",
@ -335,11 +341,13 @@ def text_table_add_metrics(strat_results: Dict) -> str:
( (
"Max Consecutive Wins / Loss", "Max Consecutive Wins / Loss",
( (
f"{strat_results['max_consecutive_wins']} / " (
f"{strat_results['max_consecutive_losses']}" f"{strat_results['max_consecutive_wins']} / "
) f"{strat_results['max_consecutive_losses']}"
if "max_consecutive_losses" in strat_results )
else "N/A", if "max_consecutive_losses" in strat_results
else "N/A"
),
), ),
("Rejected Entry signals", strat_results.get("rejected_signals", "N/A")), ("Rejected Entry signals", strat_results.get("rejected_signals", "N/A")),
( (

View File

@ -80,17 +80,19 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column
"key": first_column, "key": first_column,
"trades": len(result), "trades": len(result),
"profit_mean": result["profit_ratio"].mean() if len(result) > 0 else 0.0, "profit_mean": result["profit_ratio"].mean() if len(result) > 0 else 0.0,
"profit_mean_pct": round(result["profit_ratio"].mean() * 100.0, 2) "profit_mean_pct": (
if len(result) > 0 round(result["profit_ratio"].mean() * 100.0, 2) if len(result) > 0 else 0.0
else 0.0, ),
"profit_sum": profit_sum, "profit_sum": profit_sum,
"profit_sum_pct": round(profit_sum * 100.0, 2), "profit_sum_pct": round(profit_sum * 100.0, 2),
"profit_total_abs": result["profit_abs"].sum(), "profit_total_abs": result["profit_abs"].sum(),
"profit_total": profit_total, "profit_total": profit_total,
"profit_total_pct": round(profit_total * 100.0, 2), "profit_total_pct": round(profit_total * 100.0, 2),
"duration_avg": str(timedelta(minutes=round(result["trade_duration"].mean()))) "duration_avg": (
if not result.empty str(timedelta(minutes=round(result["trade_duration"].mean())))
else "0:00", if not result.empty
else "0:00"
),
# 'duration_max': str(timedelta( # 'duration_max': str(timedelta(
# minutes=round(result['trade_duration'].max())) # minutes=round(result['trade_duration'].max()))
# ) if not result.empty else '0:00', # ) if not result.empty else '0:00',

View File

@ -164,7 +164,8 @@ def migrate_trades_and_orders_table(
# Copy data back - following the correct schema # Copy data back - following the correct schema
with engine.begin() as connection: with engine.begin() as connection:
connection.execute( connection.execute(
text(f"""insert into trades text(
f"""insert into trades
(id, exchange, pair, base_currency, stake_currency, is_open, (id, exchange, pair, base_currency, stake_currency, is_open,
fee_open, fee_open_cost, fee_open_currency, fee_open, fee_open_cost, fee_open_currency,
fee_close, fee_close_cost, fee_close_currency, open_rate, fee_close, fee_close_cost, fee_close_currency, open_rate,
@ -209,7 +210,8 @@ def migrate_trades_and_orders_table(
{precision_mode} precision_mode, {contract_size} contract_size, {precision_mode} precision_mode, {contract_size} contract_size,
{max_stake_amount} max_stake_amount {max_stake_amount} max_stake_amount
from {trade_back_name} from {trade_back_name}
""") """
)
) )
migrate_orders_table(engine, order_back_name, cols_order) migrate_orders_table(engine, order_back_name, cols_order)
@ -238,7 +240,8 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
# sqlite does not support literals for booleans # sqlite does not support literals for booleans
with engine.begin() as connection: with engine.begin() as connection:
connection.execute( connection.execute(
text(f""" text(
f"""
insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, average, remaining, cost, status, symbol, order_type, side, price, amount, filled, average, remaining, cost,
stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee, stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee,
@ -251,7 +254,8 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
{ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason, {ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason,
{ft_order_tag} ft_order_tag {ft_order_tag} ft_order_tag
from {table_back_name} from {table_back_name}
""") """
)
) )
@ -269,13 +273,15 @@ def migrate_pairlocks_table(decl_base, inspector, engine, pairlock_back_name: st
# Copy data back - following the correct schema # Copy data back - following the correct schema
with engine.begin() as connection: with engine.begin() as connection:
connection.execute( connection.execute(
text(f"""insert into pairlocks text(
f"""insert into pairlocks
(id, pair, side, reason, lock_time, (id, pair, side, reason, lock_time,
lock_end_time, active) lock_end_time, active)
select id, pair, {side} side, reason, lock_time, select id, pair, {side} side, reason, lock_time,
lock_end_time, active lock_end_time, active
from {pairlock_back_name} from {pairlock_back_name}
""") """
)
) )

View File

@ -266,17 +266,19 @@ class Order(ModelBase):
"cost": self.cost if self.cost else 0, "cost": self.cost if self.cost else 0,
"filled": self.filled, "filled": self.filled,
"is_open": self.ft_is_open, "is_open": self.ft_is_open,
"order_date": self.order_date.strftime(DATETIME_PRINT_FORMAT) "order_date": (
if self.order_date self.order_date.strftime(DATETIME_PRINT_FORMAT) if self.order_date else None
else None, ),
"order_timestamp": int( "order_timestamp": (
self.order_date.replace(tzinfo=timezone.utc).timestamp() * 1000 int(self.order_date.replace(tzinfo=timezone.utc).timestamp() * 1000)
) if self.order_date
if self.order_date else None
else None, ),
"order_filled_date": self.order_filled_date.strftime(DATETIME_PRINT_FORMAT) "order_filled_date": (
if self.order_filled_date self.order_filled_date.strftime(DATETIME_PRINT_FORMAT)
else None, if self.order_filled_date
else None
),
"order_type": self.order_type, "order_type": self.order_type,
"price": self.price, "price": self.price,
"remaining": self.remaining, "remaining": self.remaining,

View File

@ -379,9 +379,9 @@ def pair_history_filtered(
{ {
"strategy": payload.strategy, "strategy": payload.strategy,
"timerange": payload.timerange, "timerange": payload.timerange,
"freqaimodel": payload.freqaimodel "freqaimodel": (
if payload.freqaimodel payload.freqaimodel if payload.freqaimodel else config.get("freqaimodel")
else config.get("freqaimodel"), ),
} }
) )
try: try:

View File

@ -148,9 +148,9 @@ class RPC:
"bot_name": config.get("bot_name", "freqtrade"), "bot_name": config.get("bot_name", "freqtrade"),
"timeframe": config.get("timeframe"), "timeframe": config.get("timeframe"),
"timeframe_ms": timeframe_to_msecs(config["timeframe"]) if "timeframe" in config else 0, "timeframe_ms": timeframe_to_msecs(config["timeframe"]) if "timeframe" in config else 0,
"timeframe_min": timeframe_to_minutes(config["timeframe"]) "timeframe_min": (
if "timeframe" in config timeframe_to_minutes(config["timeframe"]) if "timeframe" in config else 0
else 0, ),
"exchange": config["exchange"]["name"], "exchange": config["exchange"]["name"],
"strategy": config["strategy"], "strategy": config["strategy"],
"force_entry_enable": config.get("force_entry_enable", False), "force_entry_enable": config.get("force_entry_enable", False),
@ -404,11 +404,13 @@ class RPC:
"abs_profit": value["amount"], "abs_profit": value["amount"],
"starting_balance": value["daily_stake"], "starting_balance": value["daily_stake"],
"rel_profit": value["rel_profit"], "rel_profit": value["rel_profit"],
"fiat_value": self._fiat_converter.convert_amount( "fiat_value": (
value["amount"], stake_currency, fiat_display_currency self._fiat_converter.convert_amount(
) value["amount"], stake_currency, fiat_display_currency
if self._fiat_converter )
else 0, if self._fiat_converter
else 0
),
"trade_count": value["trades"], "trade_count": value["trades"],
} }
for key, value in profit_units.items() for key, value in profit_units.items()

View File

@ -120,13 +120,13 @@ class RPCManager:
self.send_msg( self.send_msg(
{ {
"type": RPCMessageType.STARTUP, "type": RPCMessageType.STARTUP,
"status": f'*Exchange:* `{exchange_name}`\n' "status": f"*Exchange:* `{exchange_name}`\n"
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f"*Stake per trade:* `{stake_amount} {stake_currency}`\n"
f'*Minimum ROI:* `{minimal_roi}`\n' f"*Minimum ROI:* `{minimal_roi}`\n"
f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' f"*{'Trailing ' if trailing_stop else ''}Stoploss:* `{stoploss}`\n"
f'*Position adjustment:* `{pos_adjust_enabled}`\n' f"*Position adjustment:* `{pos_adjust_enabled}`\n"
f'*Timeframe:* `{timeframe}`\n' f"*Timeframe:* `{timeframe}`\n"
f'*Strategy:* `{strategy_name}`', f"*Strategy:* `{strategy_name}`",
} }
) )
self.send_msg( self.send_msg(

View File

@ -729,9 +729,12 @@ class Telegram(RPCHandler):
lines = [ lines = [
"*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r["is_open"] else ""), "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r["is_open"] else ""),
"*Current Pair:* {pair}", "*Current Pair:* {pair}",
f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" + " ` ({leverage}x)`" (
if r.get("leverage") f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}"
else "", + " ` ({leverage}x)`"
if r.get("leverage")
else ""
),
"*Amount:* `{amount} ({stake_amount_r})`", "*Amount:* `{amount} ({stake_amount_r})`",
"*Total invested:* `{max_stake_amount_r}`" if position_adjust else "", "*Total invested:* `{max_stake_amount_r}`" if position_adjust else "",
"*Enter Tag:* `{enter_tag}`" if r["enter_tag"] else "", "*Enter Tag:* `{enter_tag}`" if r["enter_tag"] else "",
@ -753,9 +756,11 @@ class Telegram(RPCHandler):
f"*Close Rate:* `{round_value(r['close_rate'], 8)}`" if r["close_rate"] else "", f"*Close Rate:* `{round_value(r['close_rate'], 8)}`" if r["close_rate"] else "",
"*Open Date:* `{open_date}`", "*Open Date:* `{open_date}`",
"*Close Date:* `{close_date}`" if r["close_date"] else "", "*Close Date:* `{close_date}`" if r["close_date"] else "",
f" \n*Current Rate:* `{round_value(r['current_rate'], 8)}`" (
if r["is_open"] f" \n*Current Rate:* `{round_value(r['current_rate'], 8)}`"
else "", if r["is_open"]
else ""
),
("*Unrealized Profit:* " if r["is_open"] else "*Close Profit: *") ("*Unrealized Profit:* " if r["is_open"] else "*Close Profit: *")
+ "`{profit_ratio:.2%}` `({profit_abs_r})`", + "`{profit_ratio:.2%}` `({profit_abs_r})`",
] ]
@ -1072,15 +1077,19 @@ class Telegram(RPCHandler):
[ [
[ [
"Wins", "Wins",
str(timedelta(seconds=durations["wins"])) (
if durations["wins"] is not None str(timedelta(seconds=durations["wins"]))
else "N/A", if durations["wins"] is not None
else "N/A"
),
], ],
[ [
"Losses", "Losses",
str(timedelta(seconds=durations["losses"])) (
if durations["losses"] is not None str(timedelta(seconds=durations["losses"]))
else "N/A", if durations["losses"] is not None
else "N/A"
),
], ],
], ],
headers=["", "Avg. Duration"], headers=["", "Avg. Duration"],
@ -1429,7 +1438,7 @@ class Telegram(RPCHandler):
msg = self._rpc._rpc_delete(trade_id) msg = self._rpc._rpc_delete(trade_id)
await self._send_msg( await self._send_msg(
f"`{msg['result_msg']}`\n" f"`{msg['result_msg']}`\n"
'Please make sure to take care of this asset on the exchange manually.' "Please make sure to take care of this asset on the exchange manually."
) )
@authorized_only @authorized_only

View File

@ -927,9 +927,11 @@ class IStrategy(ABC, HyperStrategyMixin):
( (
p[0], p[0],
p[1], p[1],
CandleType.from_string(p[2]) (
if len(p) > 2 and p[2] != "" CandleType.from_string(p[2])
else self.config.get("candle_type_def", CandleType.SPOT), if len(p) > 2 and p[2] != ""
else self.config.get("candle_type_def", CandleType.SPOT)
),
) )
for p in informative_pairs for p in informative_pairs
] ]

View File

@ -693,7 +693,7 @@ def test_process_trade_creation(
assert log_has( assert log_has(
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT ' f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
'with stake_amount: 60.0 ...', "with stake_amount: 60.0 ...",
caplog, caplog,
) )

View File

@ -508,9 +508,9 @@ def test_min_roi_reached3(default_conf, fee) -> None:
0.09, 0.09,
0.98, 0.98,
ExitType.NONE, ExitType.NONE,
lambda current_profit, **kwargs: -0.1 lambda current_profit, **kwargs: (
if current_profit < 0.6 -0.1 if current_profit < 0.6 else -(current_profit * 2)
else -(current_profit * 2), ),
), ),
# Error case - static stoploss in place # Error case - static stoploss in place
( (

View File

@ -47,7 +47,8 @@ def test_strategy_updater_start(user_dir, capsys) -> None:
def test_strategy_updater_methods(default_conf, caplog) -> None: def test_strategy_updater_methods(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code1 = instance_strategy_updater.update_code(""" modified_code1 = instance_strategy_updater.update_code(
"""
class testClass(IStrategy): class testClass(IStrategy):
def populate_buy_trend(): def populate_buy_trend():
pass pass
@ -59,7 +60,8 @@ class testClass(IStrategy):
pass pass
def custom_sell(): def custom_sell():
pass pass
""") """
)
assert "populate_entry_trend" in modified_code1 assert "populate_entry_trend" in modified_code1
assert "populate_exit_trend" in modified_code1 assert "populate_exit_trend" in modified_code1
@ -72,11 +74,13 @@ class testClass(IStrategy):
def test_strategy_updater_params(default_conf, caplog) -> None: def test_strategy_updater_params(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code2 = instance_strategy_updater.update_code(""" modified_code2 = instance_strategy_updater.update_code(
"""
ticker_interval = '15m' ticker_interval = '15m'
buy_some_parameter = IntParameter(space='buy') buy_some_parameter = IntParameter(space='buy')
sell_some_parameter = IntParameter(space='sell') sell_some_parameter = IntParameter(space='sell')
""") """
)
assert "timeframe" in modified_code2 assert "timeframe" in modified_code2
# check for not editing hyperopt spaces # check for not editing hyperopt spaces
@ -86,13 +90,15 @@ sell_some_parameter = IntParameter(space='sell')
def test_strategy_updater_constants(default_conf, caplog) -> None: def test_strategy_updater_constants(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code3 = instance_strategy_updater.update_code(""" modified_code3 = instance_strategy_updater.update_code(
"""
use_sell_signal = True use_sell_signal = True
sell_profit_only = True sell_profit_only = True
sell_profit_offset = True sell_profit_offset = True
ignore_roi_if_buy_signal = True ignore_roi_if_buy_signal = True
forcebuy_enable = True forcebuy_enable = True
""") """
)
assert "use_exit_signal" in modified_code3 assert "use_exit_signal" in modified_code3
assert "exit_profit_only" in modified_code3 assert "exit_profit_only" in modified_code3
@ -103,10 +109,12 @@ forcebuy_enable = True
def test_strategy_updater_df_columns(default_conf, caplog) -> None: def test_strategy_updater_df_columns(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1") dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1")
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1 dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1
""") """
)
assert "enter_long" in modified_code assert "enter_long" in modified_code
assert "exit_long" in modified_code assert "exit_long" in modified_code
@ -115,18 +123,21 @@ dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1
def test_strategy_updater_method_params(default_conf, caplog) -> None: def test_strategy_updater_method_params(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
def confirm_trade_exit(sell_reason: str): def confirm_trade_exit(sell_reason: str):
nr_orders = trade.nr_of_successful_buys nr_orders = trade.nr_of_successful_buys
pass pass
""") """
)
assert "exit_reason" in modified_code assert "exit_reason" in modified_code
assert "nr_orders = trade.nr_of_successful_entries" in modified_code assert "nr_orders = trade.nr_of_successful_entries" in modified_code
def test_strategy_updater_dicts(default_conf, caplog) -> None: def test_strategy_updater_dicts(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
order_time_in_force = { order_time_in_force = {
'buy': 'gtc', 'buy': 'gtc',
'sell': 'ioc' 'sell': 'ioc'
@ -141,7 +152,8 @@ unfilledtimeout = {
'buy': 1, 'buy': 1,
'sell': 2 'sell': 2
} }
""") """
)
assert "'entry': 'gtc'" in modified_code assert "'entry': 'gtc'" in modified_code
assert "'exit': 'ioc'" in modified_code assert "'exit': 'ioc'" in modified_code
@ -153,11 +165,13 @@ unfilledtimeout = {
def test_strategy_updater_comparisons(default_conf, caplog) -> None: def test_strategy_updater_comparisons(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
def confirm_trade_exit(sell_reason): def confirm_trade_exit(sell_reason):
if (sell_reason == 'stop_loss'): if (sell_reason == 'stop_loss'):
pass pass
""") """
)
assert "exit_reason" in modified_code assert "exit_reason" in modified_code
assert "exit_reason == 'stop_loss'" in modified_code assert "exit_reason == 'stop_loss'" in modified_code
@ -165,11 +179,13 @@ def confirm_trade_exit(sell_reason):
def test_strategy_updater_strings(default_conf, caplog) -> None: def test_strategy_updater_strings(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
sell_reason == 'sell_signal' sell_reason == 'sell_signal'
sell_reason == 'force_sell' sell_reason == 'force_sell'
sell_reason == 'emergency_sell' sell_reason == 'emergency_sell'
""") """
)
# those tests currently don't work, next in line. # those tests currently don't work, next in line.
assert "exit_signal" in modified_code assert "exit_signal" in modified_code
@ -180,7 +196,8 @@ sell_reason == 'emergency_sell'
def test_strategy_updater_comments(default_conf, caplog) -> None: def test_strategy_updater_comments(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater() instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code(""" modified_code = instance_strategy_updater.update_code(
"""
# This is the 1st comment # This is the 1st comment
import talib.abstract as ta import talib.abstract as ta
# This is the 2nd comment # This is the 2nd comment
@ -197,7 +214,8 @@ class someStrategy(IStrategy):
# This is the 4th comment # This is the 4th comment
stoploss = -0.1 stoploss = -0.1
""") """
)
assert "This is the 1st comment" in modified_code assert "This is the 1st comment" in modified_code
assert "This is the 2nd comment" in modified_code assert "This is the 2nd comment" in modified_code