mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Compare commits
15 Commits
3932470190
...
73dccb0780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73dccb0780 | ||
|
|
aa7aac3a14 | ||
|
|
2015ccc727 | ||
|
|
47301830b6 | ||
|
|
b7b280de96 | ||
|
|
7edc2e8c94 | ||
|
|
972e25a6a7 | ||
|
|
649db69314 | ||
|
|
d0fb4cdc37 | ||
|
|
efabcef330 | ||
|
|
a4e6ac0c7f | ||
|
|
0ce9149ddd | ||
|
|
2750643e07 | ||
|
|
b92535deea | ||
|
|
2fa0503993 |
@@ -388,8 +388,10 @@ def refresh_backtest_ohlcv_data(
|
|||||||
for timeframe in timeframes:
|
for timeframe in timeframes:
|
||||||
# Get fast candles via parallel method on first loop through per timeframe
|
# Get fast candles via parallel method on first loop through per timeframe
|
||||||
# and candle type. Downloads all the pairs in the list and stores them.
|
# and candle type. Downloads all the pairs in the list and stores them.
|
||||||
|
# Also skips if only 1 pair/timeframe combination is scheduled for download.
|
||||||
if (
|
if (
|
||||||
not no_parallel_download
|
not no_parallel_download
|
||||||
|
and (len(pairs) + len(timeframes)) > 2
|
||||||
and exchange.get_option("download_data_parallel_quick", True)
|
and exchange.get_option("download_data_parallel_quick", True)
|
||||||
and (
|
and (
|
||||||
((pair, timeframe, candle_type) not in fast_candles)
|
((pair, timeframe, candle_type) not in fast_candles)
|
||||||
@@ -474,7 +476,7 @@ def _download_all_pairs_history_parallel(
|
|||||||
:return: Candle pairs with timeframes
|
:return: Candle pairs with timeframes
|
||||||
"""
|
"""
|
||||||
candles: dict[PairWithTimeframe, DataFrame] = {}
|
candles: dict[PairWithTimeframe, DataFrame] = {}
|
||||||
since = 0
|
since: int | None = None
|
||||||
if timerange:
|
if timerange:
|
||||||
if timerange.starttype == "date":
|
if timerange.starttype == "date":
|
||||||
since = timerange.startts * 1000
|
since = timerange.startts * 1000
|
||||||
@@ -482,10 +484,12 @@ def _download_all_pairs_history_parallel(
|
|||||||
candle_limit = exchange.ohlcv_candle_limit(timeframe, candle_type)
|
candle_limit = exchange.ohlcv_candle_limit(timeframe, candle_type)
|
||||||
one_call_min_time_dt = dt_ts(date_minus_candles(timeframe, candle_limit))
|
one_call_min_time_dt = dt_ts(date_minus_candles(timeframe, candle_limit))
|
||||||
# check if we can get all candles in one go, if so then we can download them in parallel
|
# check if we can get all candles in one go, if so then we can download them in parallel
|
||||||
if since > one_call_min_time_dt:
|
if since is None or since > one_call_min_time_dt:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Downloading parallel candles for {timeframe} for all pairs "
|
f"Downloading parallel candles for {timeframe} for all pairs"
|
||||||
f"since {format_ms_time(since)}"
|
f" since {format_ms_time(since)}"
|
||||||
|
if since
|
||||||
|
else "."
|
||||||
)
|
)
|
||||||
needed_pairs: ListPairsWithTimeframes = [
|
needed_pairs: ListPairsWithTimeframes = [
|
||||||
(p, timeframe, candle_type) for p in [p for p in pairs]
|
(p, timeframe, candle_type) for p in [p for p in pairs]
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ from freqtrade.util import (
|
|||||||
dt_ts,
|
dt_ts,
|
||||||
dt_ts_def,
|
dt_ts_def,
|
||||||
format_date,
|
format_date,
|
||||||
|
format_pct,
|
||||||
shorten_date,
|
shorten_date,
|
||||||
)
|
)
|
||||||
from freqtrade.wallets import PositionWallet, Wallet
|
from freqtrade.wallets import PositionWallet, Wallet
|
||||||
@@ -302,7 +303,7 @@ class RPC:
|
|||||||
fiat_total_profit_sum = nan
|
fiat_total_profit_sum = nan
|
||||||
for trade in self._rpc_trade_status():
|
for trade in self._rpc_trade_status():
|
||||||
# Format profit as a string with the right sign
|
# Format profit as a string with the right sign
|
||||||
profit = f"{trade['profit_ratio']:.2%}"
|
profit = f"{format_pct(trade['profit_ratio'])}"
|
||||||
fiat_profit = trade.get("profit_fiat", None)
|
fiat_profit = trade.get("profit_fiat", None)
|
||||||
if fiat_profit is None or isnan(fiat_profit):
|
if fiat_profit is None or isnan(fiat_profit):
|
||||||
fiat_profit = trade.get("profit_abs", 0.0)
|
fiat_profit = trade.get("profit_abs", 0.0)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from freqtrade.util import (
|
|||||||
fmt_coin,
|
fmt_coin,
|
||||||
fmt_coin2,
|
fmt_coin2,
|
||||||
format_date,
|
format_date,
|
||||||
|
format_pct,
|
||||||
round_value,
|
round_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -481,7 +482,7 @@ class Telegram(RPCHandler):
|
|||||||
if is_final_exit:
|
if is_final_exit:
|
||||||
profit_prefix = "Sub "
|
profit_prefix = "Sub "
|
||||||
cp_extra = (
|
cp_extra = (
|
||||||
f"*Final Profit:* `{msg['final_profit_ratio']:.2%} "
|
f"*Final Profit:* `{format_pct(msg['final_profit_ratio'])} "
|
||||||
f"({msg['cumulative_profit']:.8f} {msg['quote_currency']}{cp_fiat})`\n"
|
f"({msg['cumulative_profit']:.8f} {msg['quote_currency']}{cp_fiat})`\n"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -497,7 +498,7 @@ class Telegram(RPCHandler):
|
|||||||
f"{exit_wording} {msg['pair']} (#{msg['trade_id']})\n"
|
f"{exit_wording} {msg['pair']} (#{msg['trade_id']})\n"
|
||||||
f"{self._add_analyzed_candle(msg['pair'])}"
|
f"{self._add_analyzed_candle(msg['pair'])}"
|
||||||
f"*{f'{profit_prefix}Profit' if is_fill else f'Unrealized {profit_prefix}Profit'}:* "
|
f"*{f'{profit_prefix}Profit' if is_fill else f'Unrealized {profit_prefix}Profit'}:* "
|
||||||
f"`{msg['profit_ratio']:.2%}{profit_extra}`\n"
|
f"`{format_pct(msg['profit_ratio'])}{profit_extra}`\n"
|
||||||
f"{cp_extra}"
|
f"{cp_extra}"
|
||||||
f"{enter_tag}"
|
f"{enter_tag}"
|
||||||
f"*Exit Reason:* `{msg['exit_reason']}`\n"
|
f"*Exit Reason:* `{msg['exit_reason']}`\n"
|
||||||
@@ -677,7 +678,7 @@ class Telegram(RPCHandler):
|
|||||||
)
|
)
|
||||||
lines.append(
|
lines.append(
|
||||||
f"*Average {wording} Price:* {round_value(cur_entry_average, 8)} "
|
f"*Average {wording} Price:* {round_value(cur_entry_average, 8)} "
|
||||||
f"({price_to_1st_entry:.2%} from 1st entry rate)"
|
f"({format_pct(price_to_1st_entry)} from 1st entry rate)"
|
||||||
)
|
)
|
||||||
lines.append(f"*Order Filled:* {order['order_filled_date']}")
|
lines.append(f"*Order Filled:* {order['order_filled_date']}")
|
||||||
|
|
||||||
@@ -800,21 +801,23 @@ class Telegram(RPCHandler):
|
|||||||
else ""
|
else ""
|
||||||
),
|
),
|
||||||
("*Unrealized Profit:* " if r["is_open"] else "*Close Profit: *")
|
("*Unrealized Profit:* " if r["is_open"] else "*Close Profit: *")
|
||||||
+ f"`{r['profit_ratio']:.2%}` `({r['profit_abs_r']})`",
|
+ f"`{format_pct(r['profit_ratio'])}` `({r['profit_abs_r']})`",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if r["is_open"]:
|
if r["is_open"]:
|
||||||
if r.get("realized_profit"):
|
if (
|
||||||
lines.extend(
|
r.get("realized_profit") is not None
|
||||||
[
|
and r.get("realized_profit_ratio") is not None
|
||||||
f"*Realized Profit:* `{r['realized_profit_ratio']:.2%} "
|
):
|
||||||
f"({r['realized_profit_r']})`",
|
lines.append(
|
||||||
(
|
f"*Realized Profit:* `{format_pct(r['realized_profit_ratio'])} "
|
||||||
f"*Total Profit:* `{r['total_profit_ratio']:.2%} "
|
f"({r['realized_profit_r']})`"
|
||||||
f"({r['total_profit_abs_r']})`"
|
)
|
||||||
),
|
if r.get("total_profit_ratio") is not None:
|
||||||
]
|
lines.append(
|
||||||
|
f"*Total Profit:* `{format_pct(r['total_profit_ratio'])} "
|
||||||
|
f"({r['total_profit_abs_r']})`"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append empty line to improve readability
|
# Append empty line to improve readability
|
||||||
@@ -830,17 +833,17 @@ class Telegram(RPCHandler):
|
|||||||
# Adding initial stoploss only if it is different from stoploss
|
# Adding initial stoploss only if it is different from stoploss
|
||||||
lines.append(
|
lines.append(
|
||||||
f"*Initial Stoploss:* `{r['initial_stop_loss_abs']:.8f}` "
|
f"*Initial Stoploss:* `{r['initial_stop_loss_abs']:.8f}` "
|
||||||
f"`({r['initial_stop_loss_ratio']:.2%})`"
|
f"`({format_pct(r['initial_stop_loss_ratio'])})`"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Adding stoploss and stoploss percentage only if it is not None
|
# Adding stoploss and stoploss percentage only if it is not None
|
||||||
lines.append(
|
lines.append(
|
||||||
f"*Stoploss:* `{round_value(r['stop_loss_abs'], 8)}` "
|
f"*Stoploss:* `{round_value(r['stop_loss_abs'], 8)}` "
|
||||||
+ (f"`({r['stop_loss_ratio']:.2%})`" if r["stop_loss_ratio"] else "")
|
+ (f"`({format_pct(r['stop_loss_ratio'])})`" if r["stop_loss_ratio"] else "")
|
||||||
)
|
)
|
||||||
lines.append(
|
lines.append(
|
||||||
f"*Stoploss distance:* `{round_value(r['stoploss_current_dist'], 8)}` "
|
f"*Stoploss distance:* `{round_value(r['stoploss_current_dist'], 8)}` "
|
||||||
f"`({r['stoploss_current_dist_ratio']:.2%})`"
|
f"`({format_pct(r['stoploss_current_dist_ratio'])})`"
|
||||||
)
|
)
|
||||||
if open_orders := r.get("open_orders"):
|
if open_orders := r.get("open_orders"):
|
||||||
lines.append(
|
lines.append(
|
||||||
@@ -951,7 +954,7 @@ class Telegram(RPCHandler):
|
|||||||
f"{period['date']:{val.dateformat}} ({period['trade_count']})",
|
f"{period['date']:{val.dateformat}} ({period['trade_count']})",
|
||||||
f"{fmt_coin(period['abs_profit'], stats['stake_currency'])}",
|
f"{fmt_coin(period['abs_profit'], stats['stake_currency'])}",
|
||||||
f"{period['fiat_value']:.2f} {stats['fiat_display_currency']}",
|
f"{period['fiat_value']:.2f} {stats['fiat_display_currency']}",
|
||||||
f"{period['rel_profit']:.2%}",
|
f"{format_pct(period['rel_profit'])}",
|
||||||
]
|
]
|
||||||
for period in stats["data"]
|
for period in stats["data"]
|
||||||
],
|
],
|
||||||
@@ -1067,7 +1070,7 @@ class Telegram(RPCHandler):
|
|||||||
markdown_msg = (
|
markdown_msg = (
|
||||||
f"{closed_roi_label}\n"
|
f"{closed_roi_label}\n"
|
||||||
f"∙ `{fmt_coin(profit_closed_coin, stake_cur)} "
|
f"∙ `{fmt_coin(profit_closed_coin, stake_cur)} "
|
||||||
f"({profit_closed_ratio_mean:.2%}) "
|
f"({format_pct(profit_closed_ratio_mean)}) "
|
||||||
f"({profit_closed_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
f"({profit_closed_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||||
f"{fiat_closed_trades}"
|
f"{fiat_closed_trades}"
|
||||||
)
|
)
|
||||||
@@ -1080,7 +1083,7 @@ class Telegram(RPCHandler):
|
|||||||
markdown_msg += (
|
markdown_msg += (
|
||||||
f"{all_roi_label}\n"
|
f"{all_roi_label}\n"
|
||||||
f"∙ `{fmt_coin(profit_all_coin, stake_cur)} "
|
f"∙ `{fmt_coin(profit_all_coin, stake_cur)} "
|
||||||
f"({profit_all_ratio_mean:.2%}) "
|
f"({format_pct(profit_all_ratio_mean)}) "
|
||||||
f"({profit_all_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
f"({profit_all_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||||
f"{fiat_all_trades}"
|
f"{fiat_all_trades}"
|
||||||
f"*Total Trade Count:* `{trade_count}`\n"
|
f"*Total Trade Count:* `{trade_count}`\n"
|
||||||
@@ -1089,7 +1092,7 @@ class Telegram(RPCHandler):
|
|||||||
f"`{first_trade_date}`\n"
|
f"`{first_trade_date}`\n"
|
||||||
f"*Latest Trade opened:* `{latest_trade_date}`\n"
|
f"*Latest Trade opened:* `{latest_trade_date}`\n"
|
||||||
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n"
|
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n"
|
||||||
f"*Winrate:* `{winrate:.2%}`\n"
|
f"*Winrate:* `{format_pct(winrate)}`\n"
|
||||||
f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`"
|
f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1097,16 +1100,16 @@ class Telegram(RPCHandler):
|
|||||||
markdown_msg += (
|
markdown_msg += (
|
||||||
f"\n*Avg. Duration:* `{avg_duration}`\n"
|
f"\n*Avg. Duration:* `{avg_duration}`\n"
|
||||||
f"*Best Performing:* `{best_pair}: {best_pair_profit_abs} "
|
f"*Best Performing:* `{best_pair}: {best_pair_profit_abs} "
|
||||||
f"({best_pair_profit_ratio:.2%})`\n"
|
f"({format_pct(best_pair_profit_ratio)})`\n"
|
||||||
f"*Trading volume:* `{fmt_coin(stats['trading_volume'], stake_cur)}`\n"
|
f"*Trading volume:* `{fmt_coin(stats['trading_volume'], stake_cur)}`\n"
|
||||||
f"*Profit factor:* `{stats['profit_factor']:.2f}`\n"
|
f"*Profit factor:* `{stats['profit_factor']:.2f}`\n"
|
||||||
f"*Max Drawdown:* `{stats['max_drawdown']:.2%} "
|
f"*Max Drawdown:* `{format_pct(stats['max_drawdown'])} "
|
||||||
f"({fmt_coin(stats['max_drawdown_abs'], stake_cur)})`\n"
|
f"({fmt_coin(stats['max_drawdown_abs'], stake_cur)})`\n"
|
||||||
f" from `{stats['max_drawdown_start']} "
|
f" from `{stats['max_drawdown_start']} "
|
||||||
f"({fmt_coin(stats['drawdown_high'], stake_cur)})`\n"
|
f"({fmt_coin(stats['drawdown_high'], stake_cur)})`\n"
|
||||||
f" to `{stats['max_drawdown_end']} "
|
f" to `{stats['max_drawdown_end']} "
|
||||||
f"({fmt_coin(stats['drawdown_low'], stake_cur)})`\n"
|
f"({fmt_coin(stats['drawdown_low'], stake_cur)})`\n"
|
||||||
f"*Current Drawdown:* `{stats['current_drawdown']:.2%} "
|
f"*Current Drawdown:* `{format_pct(stats['current_drawdown'])} "
|
||||||
f"({fmt_coin(stats['current_drawdown_abs'], stake_cur)})`\n"
|
f"({fmt_coin(stats['current_drawdown_abs'], stake_cur)})`\n"
|
||||||
f" from `{stats['current_drawdown_start']} "
|
f" from `{stats['current_drawdown_start']} "
|
||||||
f"({fmt_coin(stats['current_drawdown_high'], stake_cur)})`\n"
|
f"({fmt_coin(stats['current_drawdown_high'], stake_cur)})`\n"
|
||||||
@@ -1559,7 +1562,7 @@ class Telegram(RPCHandler):
|
|||||||
dt_humanize_delta(dt_from_ts(trade["close_timestamp"])),
|
dt_humanize_delta(dt_from_ts(trade["close_timestamp"])),
|
||||||
f"{trade['pair']} (#{trade['trade_id']}"
|
f"{trade['pair']} (#{trade['trade_id']}"
|
||||||
f"{(' ' + ('S' if trade['is_short'] else 'L')) if nonspot else ''})",
|
f"{(' ' + ('S' if trade['is_short'] else 'L')) if nonspot else ''})",
|
||||||
f"{(trade['close_profit']):.2%} ({trade['close_profit_abs']})",
|
f"{format_pct(trade['close_profit'])} ({trade['close_profit_abs']})",
|
||||||
]
|
]
|
||||||
for trade in trades["trades"]
|
for trade in trades["trades"]
|
||||||
],
|
],
|
||||||
@@ -1623,7 +1626,7 @@ class Telegram(RPCHandler):
|
|||||||
stat_line = (
|
stat_line = (
|
||||||
f"{i + 1}.\t <code>{trade['pair']}\t"
|
f"{i + 1}.\t <code>{trade['pair']}\t"
|
||||||
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
||||||
f"({trade['profit_ratio']:.2%}) "
|
f"({format_pct(trade['profit_ratio'])}) "
|
||||||
f"({trade['count']})</code>\n"
|
f"({trade['count']})</code>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1660,7 +1663,7 @@ class Telegram(RPCHandler):
|
|||||||
stat_line = (
|
stat_line = (
|
||||||
f"{i + 1}.\t `{trade['enter_tag']}\t"
|
f"{i + 1}.\t `{trade['enter_tag']}\t"
|
||||||
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
||||||
f"({trade['profit_ratio']:.2%}) "
|
f"({format_pct(trade['profit_ratio'])}) "
|
||||||
f"({trade['count']})`\n"
|
f"({trade['count']})`\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1697,7 +1700,7 @@ class Telegram(RPCHandler):
|
|||||||
stat_line = (
|
stat_line = (
|
||||||
f"{i + 1}.\t `{trade['exit_reason']}\t"
|
f"{i + 1}.\t `{trade['exit_reason']}\t"
|
||||||
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
||||||
f"({trade['profit_ratio']:.2%}) "
|
f"({format_pct(trade['profit_ratio'])}) "
|
||||||
f"({trade['count']})`\n"
|
f"({trade['count']})`\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1734,7 +1737,7 @@ class Telegram(RPCHandler):
|
|||||||
stat_line = (
|
stat_line = (
|
||||||
f"{i + 1}.\t `{trade['mix_tag']}\t"
|
f"{i + 1}.\t `{trade['mix_tag']}\t"
|
||||||
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} "
|
||||||
f"({trade['profit_ratio']:.2%}) "
|
f"({format_pct(trade['profit_ratio'])}) "
|
||||||
f"({trade['count']})`\n"
|
f"({trade['count']})`\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from freqtrade.util.formatters import (
|
|||||||
fmt_coin,
|
fmt_coin,
|
||||||
fmt_coin2,
|
fmt_coin2,
|
||||||
format_duration,
|
format_duration,
|
||||||
|
format_pct,
|
||||||
round_value,
|
round_value,
|
||||||
)
|
)
|
||||||
from freqtrade.util.ft_precise import FtPrecise
|
from freqtrade.util.ft_precise import FtPrecise
|
||||||
@@ -44,6 +45,7 @@ __all__ = [
|
|||||||
"format_date",
|
"format_date",
|
||||||
"format_ms_time",
|
"format_ms_time",
|
||||||
"format_ms_time_det",
|
"format_ms_time_det",
|
||||||
|
"format_pct",
|
||||||
"get_dry_run_wallet",
|
"get_dry_run_wallet",
|
||||||
"FtPrecise",
|
"FtPrecise",
|
||||||
"PeriodicCache",
|
"PeriodicCache",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from numpy import isnan
|
||||||
|
|
||||||
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
|
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
|
||||||
|
|
||||||
|
|
||||||
@@ -29,6 +31,8 @@ def round_value(value: float, decimals: int, keep_trailing_zeros=False) -> str:
|
|||||||
:param keep_trailing_zeros: Keep trailing zeros "222.200" vs. "222.2"
|
:param keep_trailing_zeros: Keep trailing zeros "222.200" vs. "222.2"
|
||||||
:return: Rounded value as string
|
:return: Rounded value as string
|
||||||
"""
|
"""
|
||||||
|
if isnan(value):
|
||||||
|
return "N/A"
|
||||||
val = f"{value:.{decimals}f}"
|
val = f"{value:.{decimals}f}"
|
||||||
if not keep_trailing_zeros:
|
if not keep_trailing_zeros:
|
||||||
val = strip_trailing_zeros(val)
|
val = strip_trailing_zeros(val)
|
||||||
@@ -80,3 +84,15 @@ def format_duration(td: timedelta) -> str:
|
|||||||
h, r = divmod(td.seconds, 3600)
|
h, r = divmod(td.seconds, 3600)
|
||||||
m, _ = divmod(r, 60)
|
m, _ = divmod(r, 60)
|
||||||
return f"{d}d {h:02d}:{m:02d}"
|
return f"{d}d {h:02d}:{m:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_pct(value: float | None) -> str:
|
||||||
|
"""
|
||||||
|
Format a float value as percentage string with 2 decimals
|
||||||
|
None and NaN values are formatted as "N/A"
|
||||||
|
:param value: Float value to format
|
||||||
|
:return: Formatted percentage string
|
||||||
|
"""
|
||||||
|
if value is None or isnan(value):
|
||||||
|
return "N/A"
|
||||||
|
return f"{value:.2%}"
|
||||||
|
|||||||
@@ -582,6 +582,23 @@ def test_refresh_backtest_ohlcv_data(
|
|||||||
assert log_has_re(r"Downloading pair ETH/BTC, funding_rate, interval 8h\.", caplog)
|
assert log_has_re(r"Downloading pair ETH/BTC, funding_rate, interval 8h\.", caplog)
|
||||||
assert log_has_re(r"Downloading pair ETH/BTC, mark, interval 4h\.", caplog)
|
assert log_has_re(r"Downloading pair ETH/BTC, mark, interval 4h\.", caplog)
|
||||||
|
|
||||||
|
# Test with only one pair - no parallel download should happen 1 pair/timeframe combination
|
||||||
|
# doesn't justify parallelization
|
||||||
|
parallel_mock.reset_mock()
|
||||||
|
dl_mock.reset_mock()
|
||||||
|
refresh_backtest_ohlcv_data(
|
||||||
|
exchange=ex,
|
||||||
|
pairs=[
|
||||||
|
"ETH/BTC",
|
||||||
|
],
|
||||||
|
timeframes=["5m"],
|
||||||
|
datadir=testdatadir,
|
||||||
|
timerange=timerange,
|
||||||
|
erase=False,
|
||||||
|
trading_mode=trademode,
|
||||||
|
)
|
||||||
|
assert parallel_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||||
dl_mock = mocker.patch(
|
dl_mock = mocker.patch(
|
||||||
@@ -780,6 +797,7 @@ def test_download_all_pairs_history_parallel(mocker, default_conf_usdt):
|
|||||||
exchange.refresh_latest_ohlcv.reset_mock()
|
exchange.refresh_latest_ohlcv.reset_mock()
|
||||||
|
|
||||||
# Test without timerange
|
# Test without timerange
|
||||||
|
# expected to call refresh_latest_ohlcv - as we can't know how much will be required.
|
||||||
result3 = _download_all_pairs_history_parallel(
|
result3 = _download_all_pairs_history_parallel(
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
@@ -787,8 +805,8 @@ def test_download_all_pairs_history_parallel(mocker, default_conf_usdt):
|
|||||||
candle_type=candle_type,
|
candle_type=candle_type,
|
||||||
timerange=None,
|
timerange=None,
|
||||||
)
|
)
|
||||||
assert result3 == {}
|
assert result3 == expected
|
||||||
assert exchange.refresh_latest_ohlcv.call_count == 0
|
assert exchange.refresh_latest_ohlcv.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_download_pair_history_with_pair_candles(mocker, default_conf, tmp_path, caplog) -> None:
|
def test_download_pair_history_with_pair_candles(mocker, default_conf, tmp_path, caplog) -> None:
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker, time_machine) -> No
|
|||||||
)
|
)
|
||||||
assert "now" == result[0][2]
|
assert "now" == result[0][2]
|
||||||
assert "ETH/BTC" in result[0][1]
|
assert "ETH/BTC" in result[0][1]
|
||||||
assert "nan%" == result[0][3]
|
assert "N/A" == result[0][3]
|
||||||
assert isnan(fiat_profit_sum)
|
assert isnan(fiat_profit_sum)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from freqtrade.util import decimals_per_coin, fmt_coin, fmt_coin2, format_duration, round_value
|
from freqtrade.util import (
|
||||||
|
decimals_per_coin,
|
||||||
|
fmt_coin,
|
||||||
|
fmt_coin2,
|
||||||
|
format_duration,
|
||||||
|
format_pct,
|
||||||
|
round_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_decimals_per_coin():
|
def test_decimals_per_coin():
|
||||||
@@ -25,6 +32,7 @@ def test_fmt_coin():
|
|||||||
assert fmt_coin(0.1274512123, "BTC", False) == "0.12745121"
|
assert fmt_coin(0.1274512123, "BTC", False) == "0.12745121"
|
||||||
assert fmt_coin(0.1274512123, "ETH", False) == "0.12745"
|
assert fmt_coin(0.1274512123, "ETH", False) == "0.12745"
|
||||||
assert fmt_coin(222.2, "USDT", False, True) == "222.200"
|
assert fmt_coin(222.2, "USDT", False, True) == "222.200"
|
||||||
|
assert fmt_coin(float("nan"), "USDT", False, True) == "N/A"
|
||||||
|
|
||||||
|
|
||||||
def test_fmt_coin2():
|
def test_fmt_coin2():
|
||||||
@@ -35,6 +43,7 @@ def test_fmt_coin2():
|
|||||||
assert fmt_coin2(0.1274512123, "BTC") == "0.12745121 BTC"
|
assert fmt_coin2(0.1274512123, "BTC") == "0.12745121 BTC"
|
||||||
assert fmt_coin2(0.1274512123, "ETH") == "0.12745121 ETH"
|
assert fmt_coin2(0.1274512123, "ETH") == "0.12745121 ETH"
|
||||||
assert fmt_coin2(0.00001245, "PEPE") == "0.00001245 PEPE"
|
assert fmt_coin2(0.00001245, "PEPE") == "0.00001245 PEPE"
|
||||||
|
assert fmt_coin2(float("nan"), "PEPE") == "N/A PEPE"
|
||||||
|
|
||||||
|
|
||||||
def test_round_value():
|
def test_round_value():
|
||||||
@@ -46,6 +55,8 @@ def test_round_value():
|
|||||||
assert round_value(0.1274512123, 5) == "0.12745"
|
assert round_value(0.1274512123, 5) == "0.12745"
|
||||||
assert round_value(222.2, 3, True) == "222.200"
|
assert round_value(222.2, 3, True) == "222.200"
|
||||||
assert round_value(222.2, 0, True) == "222"
|
assert round_value(222.2, 0, True) == "222"
|
||||||
|
assert round_value(float("nan"), 0, True) == "N/A"
|
||||||
|
assert round_value(float("nan"), 10, True) == "N/A"
|
||||||
|
|
||||||
|
|
||||||
def test_format_duration():
|
def test_format_duration():
|
||||||
@@ -55,3 +66,13 @@ def test_format_duration():
|
|||||||
assert format_duration(timedelta(minutes=1445)) == "1d 00:05"
|
assert format_duration(timedelta(minutes=1445)) == "1d 00:05"
|
||||||
assert format_duration(timedelta(minutes=11445)) == "7d 22:45"
|
assert format_duration(timedelta(minutes=11445)) == "7d 22:45"
|
||||||
assert format_duration(timedelta(minutes=101445)) == "70d 10:45"
|
assert format_duration(timedelta(minutes=101445)) == "70d 10:45"
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_pct():
|
||||||
|
assert format_pct(0.1234) == "12.34%"
|
||||||
|
assert format_pct(0.1) == "10.00%"
|
||||||
|
assert format_pct(0.0) == "0.00%"
|
||||||
|
assert format_pct(-0.0567) == "-5.67%"
|
||||||
|
assert format_pct(-1.5567) == "-155.67%"
|
||||||
|
assert format_pct(None) == "N/A"
|
||||||
|
assert format_pct(float("nan")) == "N/A"
|
||||||
|
|||||||
Reference in New Issue
Block a user