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}'):",
"default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
if val == UNLIMITED_STAKE_AMOUNT
else val,
"filter": lambda val: (
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
),
},
{
"type": "text",

View File

@ -340,9 +340,9 @@ AVAILABLE_CLI_OPTIONS = {
"hyperopt_loss": Arg(
"--hyperopt-loss",
"--hyperoptloss",
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). '
'Different functions can generate completely different results, '
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: '
help="Specify the class name of the hyperopt loss function class (IHyperOptLoss). "
"Different functions can generate completely different results, "
"since the target for optimization is different. Built-in Hyperopt-loss-functions are: "
f'{", ".join(HYPEROPT_LOSS_BUILTIN)}',
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, "")
for k in reversed(key.split("__")):
val = {
k.lower(): _get_var_typed(val)
k.lower(): (
_get_var_typed(val)
if not isinstance(val, dict) and k not in no_convert
else val
)
}
relevant_vars = deep_merge_dicts(val, relevant_vars)
return relevant_vars

View File

@ -262,7 +262,7 @@ def _download_pair_history(
logger.info(
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'{format_ms_time(until_ms) if until_ms else "now"}'
)
@ -280,9 +280,11 @@ def _download_pair_history(
new_data = exchange.get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=since_ms
since_ms=(
since_ms
if since_ms
else int((datetime.now() - timedelta(days=new_pairs_days)).timestamp()) * 1000,
else int((datetime.now() - timedelta(days=new_pairs_days)).timestamp()) * 1000
),
is_new_pair=data.empty,
candle_type=candle_type,
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()
if not exchange:
raise OperationalException(
f'This command requires a configured exchange. You should either use '
f'`--exchange <exchange_name>` or specify a configuration file via `--config`.\n'
f'The following exchanges are available for Freqtrade: '
f"This command requires a configured exchange. You should either use "
f"`--exchange <exchange_name>` or specify a configuration file via `--config`.\n"
f"The following exchanges are available for Freqtrade: "
f'{", ".join(available_exchanges())}'
)
if not is_exchange_known_ccxt(exchange):
raise OperationalException(
f'Exchange "{exchange}" is not known to the ccxt library '
f'and therefore not available for the bot.\n'
f'The following exchanges are available for Freqtrade: '
f"and therefore not available for the bot.\n"
f"The following exchanges are available for Freqtrade: "
f'{", ".join(available_exchanges())}'
)

View File

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

View File

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

View File

@ -457,12 +457,14 @@ class HyperoptTools:
lambda x: f"{x:,.2%}".rjust(7, " ") if not isna(x) else "--".rjust(7, " ")
)
trials["Avg duration"] = trials["Avg duration"].apply(
lambda x: f"{x:,.1f} m".rjust(7, " ")
lambda x: (
f"{x:,.1f} m".rjust(7, " ")
if isinstance(x, float)
else f"{x}"
if not isna(x)
else "--".rjust(7, " ")
)
)
trials["Objective"] = trials["Objective"].apply(
lambda x: f"{x:,.5f}".rjust(8, " ") if x != 100000 else "N/A".rjust(8, " ")
)
@ -470,7 +472,8 @@ class HyperoptTools:
stake_currency = config["stake_currency"]
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
lambda x: "{} {}".format(
lambda x: (
"{} {}".format(
fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True),
(
f"({x['max_drawdown_account']:,.2%})"
@ -479,19 +482,22 @@ class HyperoptTools:
).rjust(10, " "),
).rjust(25 + len(stake_currency))
if x["max_drawdown"] != 0.0 or x["max_drawdown_account"] != 0.0
else "--".rjust(25 + len(stake_currency)),
else "--".rjust(25 + len(stake_currency))
),
axis=1,
)
trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown", "max_drawdown_account"])
trials["Profit"] = trials.apply(
lambda x: "{} {}".format(
lambda x: (
"{} {}".format(
fmt_coin(x["Total profit"], stake_currency, keep_trailing_zeros=True),
f"({x['Profit']:,.2%})".rjust(10, " "),
).rjust(25 + len(stake_currency))
if x["Total profit"] != 0.0
else "--".rjust(25 + len(stake_currency)),
else "--".rjust(25 + len(stake_currency))
),
axis=1,
)
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)
output = [
[
(
t["key"]
if t.get("key") is not None and len(str(t["key"])) > 0
else t.get(fallback, "OTHER"),
else t.get(fallback, "OTHER")
),
t["trades"],
t["profit_mean_pct"],
t["profit_total_abs"],
@ -218,9 +220,11 @@ def text_table_add_metrics(strat_results: Dict) -> str:
)
drawdown_metrics.extend(
[
(
("Absolute Drawdown (Account)", f"{strat_results['max_drawdown_account']:.2%}")
if "max_drawdown_account" in strat_results
else ("Drawdown", f"{strat_results['max_drawdown']:.2%}"),
else ("Drawdown", f"{strat_results['max_drawdown']:.2%}")
),
(
"Absolute Drawdown",
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"),
(
"Profit factor",
(
f'{strat_results["profit_factor"]:.2f}'
if "profit_factor" in strat_results
else "N/A",
else "N/A"
),
),
(
"Expectancy (Ratio)",
@ -334,12 +340,14 @@ def text_table_add_metrics(strat_results: Dict) -> str:
("Avg. Duration Loser", f"{strat_results['loser_holding_avg']}"),
(
"Max Consecutive Wins / Loss",
(
(
f"{strat_results['max_consecutive_wins']} / "
f"{strat_results['max_consecutive_losses']}"
)
if "max_consecutive_losses" in strat_results
else "N/A",
else "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,
"trades": len(result),
"profit_mean": result["profit_ratio"].mean() if len(result) > 0 else 0.0,
"profit_mean_pct": round(result["profit_ratio"].mean() * 100.0, 2)
if len(result) > 0
else 0.0,
"profit_mean_pct": (
round(result["profit_ratio"].mean() * 100.0, 2) if len(result) > 0 else 0.0
),
"profit_sum": profit_sum,
"profit_sum_pct": round(profit_sum * 100.0, 2),
"profit_total_abs": result["profit_abs"].sum(),
"profit_total": profit_total,
"profit_total_pct": round(profit_total * 100.0, 2),
"duration_avg": str(timedelta(minutes=round(result["trade_duration"].mean())))
"duration_avg": (
str(timedelta(minutes=round(result["trade_duration"].mean())))
if not result.empty
else "0:00",
else "0:00"
),
# 'duration_max': str(timedelta(
# minutes=round(result['trade_duration'].max()))
# ) 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
with engine.begin() as connection:
connection.execute(
text(f"""insert into trades
text(
f"""insert into trades
(id, exchange, pair, base_currency, stake_currency, is_open,
fee_open, fee_open_cost, fee_open_currency,
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,
{max_stake_amount} max_stake_amount
from {trade_back_name}
""")
"""
)
)
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
with engine.begin() as connection:
connection.execute(
text(f"""
text(
f"""
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,
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_order_tag} ft_order_tag
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
with engine.begin() as connection:
connection.execute(
text(f"""insert into pairlocks
text(
f"""insert into pairlocks
(id, pair, side, reason, lock_time,
lock_end_time, active)
select id, pair, {side} side, reason, lock_time,
lock_end_time, active
from {pairlock_back_name}
""")
"""
)
)

View File

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

View File

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

View File

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

View File

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

View File

@ -729,9 +729,12 @@ class Telegram(RPCHandler):
lines = [
"*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r["is_open"] else ""),
"*Current Pair:* {pair}",
f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" + " ` ({leverage}x)`"
(
f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}"
+ " ` ({leverage}x)`"
if r.get("leverage")
else "",
else ""
),
"*Amount:* `{amount} ({stake_amount_r})`",
"*Total invested:* `{max_stake_amount_r}`" if position_adjust 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 "",
"*Open Date:* `{open_date}`",
"*Close Date:* `{close_date}`" if r["close_date"] else "",
(
f" \n*Current Rate:* `{round_value(r['current_rate'], 8)}`"
if r["is_open"]
else "",
else ""
),
("*Unrealized Profit:* " if r["is_open"] else "*Close Profit: *")
+ "`{profit_ratio:.2%}` `({profit_abs_r})`",
]
@ -1072,15 +1077,19 @@ class Telegram(RPCHandler):
[
[
"Wins",
(
str(timedelta(seconds=durations["wins"]))
if durations["wins"] is not None
else "N/A",
else "N/A"
),
],
[
"Losses",
(
str(timedelta(seconds=durations["losses"]))
if durations["losses"] is not None
else "N/A",
else "N/A"
),
],
],
headers=["", "Avg. Duration"],
@ -1429,7 +1438,7 @@ class Telegram(RPCHandler):
msg = self._rpc._rpc_delete(trade_id)
await self._send_msg(
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

View File

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

View File

@ -693,7 +693,7 @@ def test_process_trade_creation(
assert log_has(
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,
)

View File

@ -508,9 +508,9 @@ def test_min_roi_reached3(default_conf, fee) -> None:
0.09,
0.98,
ExitType.NONE,
lambda current_profit, **kwargs: -0.1
if current_profit < 0.6
else -(current_profit * 2),
lambda current_profit, **kwargs: (
-0.1 if current_profit < 0.6 else -(current_profit * 2)
),
),
# 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:
instance_strategy_updater = StrategyUpdater()
modified_code1 = instance_strategy_updater.update_code("""
modified_code1 = instance_strategy_updater.update_code(
"""
class testClass(IStrategy):
def populate_buy_trend():
pass
@ -59,7 +60,8 @@ class testClass(IStrategy):
pass
def custom_sell():
pass
""")
"""
)
assert "populate_entry_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:
instance_strategy_updater = StrategyUpdater()
modified_code2 = instance_strategy_updater.update_code("""
modified_code2 = instance_strategy_updater.update_code(
"""
ticker_interval = '15m'
buy_some_parameter = IntParameter(space='buy')
sell_some_parameter = IntParameter(space='sell')
""")
"""
)
assert "timeframe" in modified_code2
# 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:
instance_strategy_updater = StrategyUpdater()
modified_code3 = instance_strategy_updater.update_code("""
modified_code3 = instance_strategy_updater.update_code(
"""
use_sell_signal = True
sell_profit_only = True
sell_profit_offset = True
ignore_roi_if_buy_signal = True
forcebuy_enable = True
""")
"""
)
assert "use_exit_signal" 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:
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), 'sell'] = 1
""")
"""
)
assert "enter_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:
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):
nr_orders = trade.nr_of_successful_buys
pass
""")
"""
)
assert "exit_reason" in modified_code
assert "nr_orders = trade.nr_of_successful_entries" in modified_code
def test_strategy_updater_dicts(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code("""
modified_code = instance_strategy_updater.update_code(
"""
order_time_in_force = {
'buy': 'gtc',
'sell': 'ioc'
@ -141,7 +152,8 @@ unfilledtimeout = {
'buy': 1,
'sell': 2
}
""")
"""
)
assert "'entry': 'gtc'" in modified_code
assert "'exit': 'ioc'" in modified_code
@ -153,11 +165,13 @@ unfilledtimeout = {
def test_strategy_updater_comparisons(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code("""
modified_code = instance_strategy_updater.update_code(
"""
def confirm_trade_exit(sell_reason):
if (sell_reason == 'stop_loss'):
pass
""")
"""
)
assert "exit_reason" 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:
instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code("""
modified_code = instance_strategy_updater.update_code(
"""
sell_reason == 'sell_signal'
sell_reason == 'force_sell'
sell_reason == 'emergency_sell'
""")
"""
)
# those tests currently don't work, next in line.
assert "exit_signal" in modified_code
@ -180,7 +196,8 @@ sell_reason == 'emergency_sell'
def test_strategy_updater_comments(default_conf, caplog) -> None:
instance_strategy_updater = StrategyUpdater()
modified_code = instance_strategy_updater.update_code("""
modified_code = instance_strategy_updater.update_code(
"""
# This is the 1st comment
import talib.abstract as ta
# This is the 2nd comment
@ -197,7 +214,8 @@ class someStrategy(IStrategy):
# This is the 4th comment
stoploss = -0.1
""")
"""
)
assert "This is the 1st comment" in modified_code
assert "This is the 2nd comment" in modified_code