Merge branch 'develop' into feat/binance_trades_fast

This commit is contained in:
Matthias
2025-01-27 20:40:59 +01:00
17 changed files with 111 additions and 101 deletions

View File

@@ -560,7 +560,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Publish to PyPI (Test) - name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@v1.12.3 uses: pypa/gh-action-pypi-publish@v1.12.4
with: with:
repository-url: https://test.pypi.org/legacy/ repository-url: https://test.pypi.org/legacy/
@@ -587,7 +587,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Publish to PyPI - name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.3 uses: pypa/gh-action-pypi-publish@v1.12.4
deploy-docker: deploy-docker:

View File

@@ -2,6 +2,6 @@ markdown==3.7
mkdocs==1.6.1 mkdocs==1.6.1
mkdocs-material==9.5.50 mkdocs-material==9.5.50
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==10.14 pymdown-extensions==10.14.1
jinja2==3.1.5 jinja2==3.1.5
mike==2.1.3 mike==2.1.3

View File

@@ -444,7 +444,7 @@ def _download_trades_history(
if not trades.empty and since < trades.iloc[-1]["timestamp"]: if not trades.empty and since < trades.iloc[-1]["timestamp"]:
# Reset since to the last available point # Reset since to the last available point
# - 5 seconds (to ensure we're getting all trades) # - 5 seconds (to ensure we're getting all trades)
since = trades.iloc[-1]["timestamp"] - (5 * 1000) since = int(trades.iloc[-1]["timestamp"] - (5 * 1000))
logger.info( logger.info(
f"Using last trade date -5s - Downloading trades for {pair} " f"Using last trade date -5s - Downloading trades for {pair} "
f"since: {format_ms_time(since)}." f"since: {format_ms_time(since)}."

View File

@@ -367,7 +367,7 @@ class Binance(Exchange):
return {} return {}
async def _async_get_trade_history_id_startup( async def _async_get_trade_history_id_startup(
self, pair: str, since: int | None self, pair: str, since: int
) -> tuple[list[list], str]: ) -> tuple[list[list], str]:
""" """
override for initial call override for initial call

View File

@@ -2960,7 +2960,7 @@ class Exchange:
return trades[-1].get("timestamp") return trades[-1].get("timestamp")
async def _async_get_trade_history_id_startup( async def _async_get_trade_history_id_startup(
self, pair: str, since: int | None self, pair: str, since: int
) -> tuple[list[list], str]: ) -> tuple[list[list], str]:
""" """
override for initial trade_history_id call override for initial trade_history_id call
@@ -2968,7 +2968,7 @@ class Exchange:
return await self._async_fetch_trades(pair, since=since) return await self._async_fetch_trades(pair, since=since)
async def _async_get_trade_history_id( async def _async_get_trade_history_id(
self, pair: str, until: int, since: int | None = None, from_id: str | None = None self, pair: str, *, until: int, since: int, from_id: str | None = None
) -> tuple[str, list[list]]: ) -> tuple[str, list[list]]:
""" """
Asynchronously gets trade history using fetch_trades Asynchronously gets trade history using fetch_trades
@@ -3022,7 +3022,7 @@ class Exchange:
return (pair, trades) return (pair, trades)
async def _async_get_trade_history_time( async def _async_get_trade_history_time(
self, pair: str, until: int, since: int | None = None self, pair: str, until: int, since: int
) -> tuple[str, list[list]]: ) -> tuple[str, list[list]]:
""" """
Asynchronously gets trade history using fetch_trades, Asynchronously gets trade history using fetch_trades,
@@ -3063,7 +3063,7 @@ class Exchange:
async def _async_get_trade_history( async def _async_get_trade_history(
self, self,
pair: str, pair: str,
since: int | None = None, since: int,
until: int | None = None, until: int | None = None,
from_id: str | None = None, from_id: str | None = None,
) -> tuple[str, list[list]]: ) -> tuple[str, list[list]]:
@@ -3094,7 +3094,7 @@ class Exchange:
def get_historic_trades( def get_historic_trades(
self, self,
pair: str, pair: str,
since: int | None = None, since: int,
until: int | None = None, until: int | None = None,
from_id: str | None = None, from_id: str | None = None,
) -> tuple[str, list]: ) -> tuple[str, list]:

View File

@@ -6,7 +6,7 @@ import logging
from collections import defaultdict from collections import defaultdict
from collections.abc import Sequence from collections.abc import Sequence
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta, timezone from datetime import datetime, timezone
from math import isclose from math import isclose
from typing import Any, ClassVar, Optional, cast from typing import Any, ClassVar, Optional, cast
@@ -1914,19 +1914,20 @@ class Trade(ModelBase, LocalTrade):
return total_open_stake_amount or 0 return total_open_stake_amount or 0
@staticmethod @staticmethod
def get_overall_performance(minutes=None) -> list[dict[str, Any]]: def _generic_performance_query(columns: list, filters: list, fallback: str = "") -> Select:
""" """
Returns List of dicts containing all Trades, including profit and trade count Retrieve a generic select object to calculate performance grouped on `columns`.
Returns the following columns:
- columns
- profit_ratio
- profit_sum_abs
- count
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
""" """
filters: list = [Trade.is_open.is_(False)] columns_coal = [func.coalesce(c, fallback).label(c.name) for c in columns]
if minutes:
start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes)
filters.append(Trade.close_date >= start_date)
pair_costs = ( pair_costs = (
select( select(
Trade.pair, *columns_coal,
func.sum( func.sum(
( (
func.coalesce(Order.filled, Order.amount) func.coalesce(Order.filled, Order.amount)
@@ -1939,35 +1940,46 @@ class Trade(ModelBase, LocalTrade):
.filter( .filter(
*filters, *filters,
Order.ft_order_side == case((Trade.is_short.is_(True), "sell"), else_="buy"), Order.ft_order_side == case((Trade.is_short.is_(True), "sell"), else_="buy"),
Order.filled > 0,
) )
# Order.filled.gt > 0 .group_by(*columns)
.group_by(Trade.pair)
.cte("pair_costs") .cte("pair_costs")
) )
trades_grouped = ( trades_grouped = (
select( select(
Trade.pair, *columns_coal,
func.sum(Trade.close_profit_abs).label("profit_sum_abs"), func.sum(Trade.close_profit_abs).label("profit_sum_abs"),
func.count(Trade.pair).label("count"), func.count(*columns_coal).label("count"),
) )
.filter(*filters) .filter(*filters)
.group_by(Trade.pair) .group_by(*columns_coal)
.cte("trades_grouped") .cte("trades_grouped")
) )
q = ( q = (
select( select(
trades_grouped.c.pair, *[trades_grouped.c[x.name] for x in columns],
(trades_grouped.c.profit_sum_abs / pair_costs.c.cost_per_pair).label( (trades_grouped.c.profit_sum_abs / pair_costs.c.cost_per_pair).label(
"profit_ratio" "profit_ratio"
), ),
trades_grouped.c.profit_sum_abs, trades_grouped.c.profit_sum_abs,
trades_grouped.c.count, trades_grouped.c.count,
) )
.join(pair_costs, trades_grouped.c.pair == pair_costs.c.pair) .join(pair_costs, *[trades_grouped.c[x.name] == pair_costs.c[x.name] for x in columns])
.order_by(desc("profit_sum_abs")) .order_by(desc("profit_sum_abs"))
) )
pair_rates = Trade.session.execute(q).all() return q
@staticmethod
def get_overall_performance(start_date: datetime | None = None) -> list[dict[str, Any]]:
"""
Returns List of dicts containing all Trades, including profit and trade count
NOTE: Not supported in Backtesting.
"""
filters: list = [Trade.is_open.is_(False)]
if start_date:
filters.append(Trade.close_date >= start_date)
pair_rates_query = Trade._generic_performance_query([Trade.pair], filters)
pair_rates = Trade.session.execute(pair_rates_query).all()
return [ return [
{ {
"pair": pair, "pair": pair,
@@ -1992,17 +2004,8 @@ class Trade(ModelBase, LocalTrade):
if pair is not None: if pair is not None:
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
enter_tag_perf = Trade.session.execute( pair_rates_query = Trade._generic_performance_query([Trade.enter_tag], filters, "Other")
select( enter_tag_perf = Trade.session.execute(pair_rates_query).all()
Trade.enter_tag,
func.sum(Trade.close_profit).label("profit_sum"),
func.sum(Trade.close_profit_abs).label("profit_sum_abs"),
func.count(Trade.pair).label("count"),
)
.filter(*filters)
.group_by(Trade.enter_tag)
.order_by(desc("profit_sum_abs"))
).all()
return [ return [
{ {
@@ -2026,17 +2029,9 @@ class Trade(ModelBase, LocalTrade):
filters: list = [Trade.is_open.is_(False)] filters: list = [Trade.is_open.is_(False)]
if pair is not None: if pair is not None:
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
sell_tag_perf = Trade.session.execute(
select( pair_rates_query = Trade._generic_performance_query([Trade.exit_reason], filters, "Other")
Trade.exit_reason, sell_tag_perf = Trade.session.execute(pair_rates_query).all()
func.sum(Trade.close_profit).label("profit_sum"),
func.sum(Trade.close_profit_abs).label("profit_sum_abs"),
func.count(Trade.pair).label("count"),
)
.filter(*filters)
.group_by(Trade.exit_reason)
.order_by(desc("profit_sum_abs"))
).all()
return [ return [
{ {
@@ -2117,13 +2112,9 @@ class Trade(ModelBase, LocalTrade):
if start_date: if start_date:
filters.append(Trade.close_date >= start_date) filters.append(Trade.close_date >= start_date)
best_pair = Trade.session.execute( pair_rates_query = Trade._generic_performance_query([Trade.pair], filters)
select(Trade.pair, func.sum(Trade.close_profit).label("profit_sum")) best_pair = Trade.session.execute(pair_rates_query).first()
.filter(*filters) # returns pair, profit_ratio, abs_profit, count
.group_by(Trade.pair)
.order_by(desc("profit_sum"))
).first()
return best_pair return best_pair
@staticmethod @staticmethod

View File

@@ -3,12 +3,14 @@ Performance pair list filter
""" """
import logging import logging
from datetime import timedelta
import pandas as pd import pandas as pd
from freqtrade.exchange.exchange_types import Tickers from freqtrade.exchange.exchange_types import Tickers
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util.datetime_helpers import dt_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -69,7 +71,8 @@ class PerformanceFilter(IPairList):
""" """
# Get the trading performance for pairs from database # Get the trading performance for pairs from database
try: try:
performance = pd.DataFrame(Trade.get_overall_performance(self._minutes)) start_date = dt_now() - timedelta(minutes=self._minutes)
performance = pd.DataFrame(Trade.get_overall_performance(start_date))
except AttributeError: except AttributeError:
# Performancefilter does not work in backtesting. # Performancefilter does not work in backtesting.
self.log_once("PerformanceFilter is not available in this mode.", logger.warning) self.log_once("PerformanceFilter is not available in this mode.", logger.warning)

View File

@@ -150,6 +150,7 @@ class Profit(BaseModel):
best_pair: str best_pair: str
best_rate: float best_rate: float
best_pair_profit_ratio: float best_pair_profit_ratio: float
best_pair_profit_abs: float
winning_trades: int winning_trades: int
losing_trades: int losing_trades: int
profit_factor: float profit_factor: float

View File

@@ -665,6 +665,7 @@ class RPC:
"best_pair": best_pair[0] if best_pair else "", "best_pair": best_pair[0] if best_pair else "",
"best_rate": round(best_pair[1] * 100, 2) if best_pair else 0, # Deprecated "best_rate": round(best_pair[1] * 100, 2) if best_pair else 0, # Deprecated
"best_pair_profit_ratio": best_pair[1] if best_pair else 0, "best_pair_profit_ratio": best_pair[1] if best_pair else 0,
"best_pair_profit_abs": best_pair[2] if best_pair else 0,
"winning_trades": winning_trades, "winning_trades": winning_trades,
"losing_trades": losing_trades, "losing_trades": losing_trades,
"profit_factor": profit_factor, "profit_factor": profit_factor,

View File

@@ -1028,6 +1028,7 @@ class Telegram(RPCHandler):
avg_duration = stats["avg_duration"] avg_duration = stats["avg_duration"]
best_pair = stats["best_pair"] best_pair = stats["best_pair"]
best_pair_profit_ratio = stats["best_pair_profit_ratio"] best_pair_profit_ratio = stats["best_pair_profit_ratio"]
best_pair_profit_abs = fmt_coin(stats["best_pair_profit_abs"], stake_cur)
winrate = stats["winrate"] winrate = stats["winrate"]
expectancy = stats["expectancy"] expectancy = stats["expectancy"]
expectancy_ratio = stats["expectancy_ratio"] expectancy_ratio = stats["expectancy_ratio"]
@@ -1067,7 +1068,8 @@ class Telegram(RPCHandler):
if stats["closed_trade_count"] > 0: if stats["closed_trade_count"] > 0:
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_ratio:.2%}`\n" f"*Best Performing:* `{best_pair}: {best_pair_profit_abs} "
f"({best_pair_profit_ratio:.2%})`\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:* `{stats['max_drawdown']:.2%} "

View File

@@ -7,9 +7,9 @@
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
coveralls==4.0.1 coveralls==4.0.1
ruff==0.9.2 ruff==0.9.3
mypy==1.14.1 mypy==1.14.1
pre-commit==4.0.1 pre-commit==4.1.0
pytest==8.3.4 pytest==8.3.4
pytest-asyncio==0.25.2 pytest-asyncio==0.25.2
pytest-cov==6.0.0 pytest-cov==6.0.0

View File

@@ -5,4 +5,4 @@
scipy==1.15.1 scipy==1.15.1
scikit-learn==1.6.1 scikit-learn==1.6.1
ft-scikit-optimize==0.9.2 ft-scikit-optimize==0.9.2
filelock==3.16.1 filelock==3.17.0

View File

@@ -4,7 +4,7 @@ bottleneck==1.4.2
numexpr==2.10.2 numexpr==2.10.2
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==4.4.49 ccxt==4.4.50
cryptography==42.0.8; platform_machine == 'armv7l' cryptography==42.0.8; platform_machine == 'armv7l'
cryptography==44.0.0; platform_machine != 'armv7l' cryptography==44.0.0; platform_machine != 'armv7l'
aiohttp==3.10.11 aiohttp==3.10.11
@@ -13,7 +13,7 @@ python-telegram-bot==21.10
# can't be hard-pinned due to telegram-bot pinning httpx with ~ # can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1 httpx>=0.24.1
humanize==4.11.0 humanize==4.11.0
cachetools==5.5.0 cachetools==5.5.1
requests==2.32.3 requests==2.32.3
urllib3==2.3.0 urllib3==2.3.0
jsonschema==4.23.0 jsonschema==4.23.0
@@ -38,8 +38,8 @@ orjson==3.10.15
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.115.6 fastapi==0.115.7
pydantic==2.10.5 pydantic==2.10.6
uvicorn==0.34.0 uvicorn==0.34.0
pyjwt==2.10.1 pyjwt==2.10.1
aiofiles==24.1.0 aiofiles==24.1.0
@@ -47,7 +47,7 @@ psutil==6.1.1
# Building config files interactively # Building config files interactively
questionary==2.1.0 questionary==2.1.0
prompt-toolkit==3.0.36 prompt-toolkit==3.0.50
# Extensions to datetime library # Extensions to datetime library
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
pytz==2024.2 pytz==2024.2

View File

@@ -1931,9 +1931,9 @@ def test_get_overall_performance(fee):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"is_short,pair,profit", "is_short,pair,profit",
[ [
(True, "ETC/BTC", -0.005), (True, "XRP/BTC", -0.00018780487),
(False, "XRP/BTC", 0.01), (False, "ETC/BTC", 0.00003860975),
(None, "XRP/BTC", 0.01), (None, "XRP/BTC", 0.000025203252),
], ],
) )
def test_get_best_pair(fee, is_short, pair, profit): def test_get_best_pair(fee, is_short, pair, profit):
@@ -1942,9 +1942,9 @@ def test_get_best_pair(fee, is_short, pair, profit):
create_mock_trades(fee, is_short) create_mock_trades(fee, is_short)
res = Trade.get_best_pair() res = Trade.get_best_pair()
assert len(res) == 2 assert len(res) == 4
assert res[0] == pair assert res[0] == pair
assert res[1] == profit assert pytest.approx(res[1]) == profit
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@@ -1954,9 +1954,9 @@ def test_get_best_pair_lev(fee):
create_mock_trades_with_leverage(fee) create_mock_trades_with_leverage(fee)
res = Trade.get_best_pair() res = Trade.get_best_pair()
assert len(res) == 2 assert len(res) == 4
assert res[0] == "DOGE/BTC" assert res[0] == "ETC/BTC"
assert res[1] == 0.1713156134055116 assert pytest.approx(res[1]) == 0.00003860975
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")

View File

@@ -480,8 +480,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert stats["first_trade_humanized"] == "2 days ago" assert stats["first_trade_humanized"] == "2 days ago"
assert stats["latest_trade_humanized"] == "17 minutes ago" assert stats["latest_trade_humanized"] == "17 minutes ago"
assert stats["avg_duration"] in ("0:17:40") assert stats["avg_duration"] in ("0:17:40")
assert stats["best_pair"] == "XRP/USDT" assert stats["best_pair"] == "NEO/USDT"
assert stats["best_rate"] == 10.0 assert stats["best_rate"] == 1.99
# Test non-available pair # Test non-available pair
mocker.patch( mocker.patch(
@@ -492,8 +492,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert stats["first_trade_humanized"] == "2 days ago" assert stats["first_trade_humanized"] == "2 days ago"
assert stats["latest_trade_humanized"] == "17 minutes ago" assert stats["latest_trade_humanized"] == "17 minutes ago"
assert stats["avg_duration"] in ("0:17:40") assert stats["avg_duration"] in ("0:17:40")
assert stats["best_pair"] == "XRP/USDT" assert stats["best_pair"] == "NEO/USDT"
assert stats["best_rate"] == 10.0 assert stats["best_rate"] == 1.99
assert isnan(stats["profit_all_coin"]) assert isnan(stats["profit_all_coin"])
@@ -1018,14 +1018,14 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None
assert len(res) == 3 assert len(res) == 3
assert res[0]["enter_tag"] == "TEST1" assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0 assert res[0]["profit_pct"] == 1.99
res = rpc._rpc_enter_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 3 assert len(res) == 3
assert res[0]["enter_tag"] == "TEST1" assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0 assert res[0]["profit_pct"] == 1.99
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
@@ -1041,17 +1041,20 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert len(res) == 2 assert len(res) == 2
assert res[0]["enter_tag"] == "TEST1" assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert pytest.approx(res[0]["profit_pct"]) == 0.5 assert pytest.approx(res[0]["profit_pct"]) == 0.0
assert pytest.approx(res[0]["profit_ratio"]) == 0.00003860975
assert res[1]["enter_tag"] == "Other" assert res[1]["enter_tag"] == "Other"
assert res[1]["count"] == 1 assert res[1]["count"] == 1
assert pytest.approx(res[1]["profit_pct"]) == 1.0 assert pytest.approx(res[1]["profit_pct"]) == 0.0
assert pytest.approx(res[1]["profit_ratio"]) == 0.00002520325
# Test for a specific pair # Test for a specific pair
res = rpc._rpc_enter_tag_performance("ETC/BTC") res = rpc._rpc_enter_tag_performance("ETC/BTC")
assert len(res) == 1 assert len(res) == 1
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert res[0]["enter_tag"] == "TEST1" assert res[0]["enter_tag"] == "TEST1"
assert pytest.approx(res[0]["profit_pct"]) == 0.5 assert pytest.approx(res[0]["profit_pct"]) == 0.0
assert pytest.approx(res[0]["profit_ratio"]) == 0.00003860975
def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
@@ -1075,7 +1078,7 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker)
assert len(res) == 3 assert len(res) == 3
assert res[0]["exit_reason"] == "exit_signal" assert res[0]["exit_reason"] == "exit_signal"
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0 assert res[0]["profit_pct"] == 1.99
assert res[1]["exit_reason"] == "roi" assert res[1]["exit_reason"] == "roi"
assert res[2]["exit_reason"] == "Other" assert res[2]["exit_reason"] == "Other"
@@ -1094,17 +1097,20 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
assert len(res) == 2 assert len(res) == 2
assert res[0]["exit_reason"] == "sell_signal" assert res[0]["exit_reason"] == "sell_signal"
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert pytest.approx(res[0]["profit_pct"]) == 0.5 assert pytest.approx(res[0]["profit_pct"]) == 0.0
assert pytest.approx(res[0]["profit_ratio"]) == 0.00003860975
assert res[1]["exit_reason"] == "roi" assert res[1]["exit_reason"] == "roi"
assert res[1]["count"] == 1 assert res[1]["count"] == 1
assert pytest.approx(res[1]["profit_pct"]) == 1.0 assert pytest.approx(res[1]["profit_pct"]) == 0.0
assert pytest.approx(res[1]["profit_ratio"]) == 0.000025203252
# Test for a specific pair # Test for a specific pair
res = rpc._rpc_exit_reason_performance("ETC/BTC") res = rpc._rpc_exit_reason_performance("ETC/BTC")
assert len(res) == 1 assert len(res) == 1
assert res[0]["count"] == 1 assert res[0]["count"] == 1
assert res[0]["exit_reason"] == "sell_signal" assert res[0]["exit_reason"] == "sell_signal"
assert pytest.approx(res[0]["profit_pct"]) == 0.5 assert pytest.approx(res[0]["profit_pct"]) == 0.0
assert pytest.approx(res[0]["profit_ratio"]) == 0.00003860975
def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:

View File

@@ -963,9 +963,10 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
( (
True, True,
{ {
"best_pair": "ETC/BTC", "best_pair": "XRP/BTC",
"best_rate": -0.5, "best_rate": -0.02,
"best_pair_profit_ratio": -0.005, "best_pair_profit_ratio": -0.00018780487,
"best_pair_profit_abs": -0.001155,
"profit_all_coin": 15.382312, "profit_all_coin": 15.382312,
"profit_all_fiat": 189894.6470718, "profit_all_fiat": 189894.6470718,
"profit_all_percent_mean": 49.62, "profit_all_percent_mean": 49.62,
@@ -994,9 +995,10 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
( (
False, False,
{ {
"best_pair": "XRP/BTC", "best_pair": "ETC/BTC",
"best_rate": 1.0, "best_rate": 0.0,
"best_pair_profit_ratio": 0.01, "best_pair_profit_ratio": 0.00003860975,
"best_pair_profit_abs": 0.000584127,
"profit_all_coin": -15.46546305, "profit_all_coin": -15.46546305,
"profit_all_fiat": -190921.14135225, "profit_all_fiat": -190921.14135225,
"profit_all_percent_mean": -49.62, "profit_all_percent_mean": -49.62,
@@ -1026,8 +1028,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
None, None,
{ {
"best_pair": "XRP/BTC", "best_pair": "XRP/BTC",
"best_rate": 1.0, "best_rate": 0.0,
"best_pair_profit_ratio": 0.01, "best_pair_profit_ratio": 0.000025203252,
"best_pair_profit_abs": 0.000155,
"profit_all_coin": -14.87167525, "profit_all_coin": -14.87167525,
"profit_all_fiat": -183590.83096125, "profit_all_fiat": -183590.83096125,
"profit_all_percent_mean": 0.13, "profit_all_percent_mean": 0.13,
@@ -1080,7 +1083,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
assert rc.json() == { assert rc.json() == {
"avg_duration": ANY, "avg_duration": ANY,
"best_pair": expected["best_pair"], "best_pair": expected["best_pair"],
"best_pair_profit_ratio": expected["best_pair_profit_ratio"], "best_pair_profit_ratio": pytest.approx(expected["best_pair_profit_ratio"]),
"best_pair_profit_abs": expected["best_pair_profit_abs"],
"best_rate": expected["best_rate"], "best_rate": expected["best_rate"],
"first_trade_date": ANY, "first_trade_date": ANY,
"first_trade_humanized": ANY, "first_trade_humanized": ANY,
@@ -1206,7 +1210,8 @@ def test_api_entries(botclient, fee):
resp = response[0] resp = response[0]
assert resp["enter_tag"] == "TEST1" assert resp["enter_tag"] == "TEST1"
assert resp["count"] == 1 assert resp["count"] == 1
assert resp["profit_pct"] == 0.5 assert resp["profit_pct"] == 0.0
assert pytest.approx(resp["profit_ratio"]) == 0.000038609756
def test_api_exits(botclient, fee): def test_api_exits(botclient, fee):
@@ -1225,7 +1230,8 @@ def test_api_exits(botclient, fee):
resp = response[0] resp = response[0]
assert resp["exit_reason"] == "sell_signal" assert resp["exit_reason"] == "sell_signal"
assert resp["count"] == 1 assert resp["count"] == 1
assert resp["profit_pct"] == 0.5 assert resp["profit_pct"] == 0.0
assert pytest.approx(resp["profit_ratio"]) == 0.000038609756
def test_api_mix_tag(botclient, fee): def test_api_mix_tag(botclient, fee):

View File

@@ -918,7 +918,7 @@ async def test_telegram_profit_handle(
) )
assert "∙ `6.253 USD`" in msg_mock.call_args_list[-1][0][0] assert "∙ `6.253 USD`" in msg_mock.call_args_list[-1][0][0]
assert "*Best Performing:* `ETH/USDT: 9.45%`" in msg_mock.call_args_list[-1][0][0] assert "*Best Performing:* `ETH/USDT: 5.685 USDT (9.47%)`" in msg_mock.call_args_list[-1][0][0]
assert "*Max Drawdown:*" in msg_mock.call_args_list[-1][0][0] assert "*Max Drawdown:*" in msg_mock.call_args_list[-1][0][0]
assert "*Profit factor:*" in msg_mock.call_args_list[-1][0][0] assert "*Profit factor:*" in msg_mock.call_args_list[-1][0][0]
assert "*Winrate:*" in msg_mock.call_args_list[-1][0][0] assert "*Winrate:*" in msg_mock.call_args_list[-1][0][0]
@@ -1611,7 +1611,7 @@ async def test_telegram_entry_tag_performance_handle(
await telegram._enter_tag_performance(update=update, context=context) await telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "Entry Tag Performance" in msg_mock.call_args_list[0][0][0] assert "Entry Tag Performance" in msg_mock.call_args_list[0][0][0]
assert "`TEST1\t3.987 USDT (5.00%) (1)`" in msg_mock.call_args_list[0][0][0] assert "`TEST1\t3.987 USDT (1.99%) (1)`" in msg_mock.call_args_list[0][0][0]
context.args = ["XRP/USDT"] context.args = ["XRP/USDT"]
await telegram._enter_tag_performance(update=update, context=context) await telegram._enter_tag_performance(update=update, context=context)
@@ -1644,7 +1644,7 @@ async def test_telegram_exit_reason_performance_handle(
await telegram._exit_reason_performance(update=update, context=context) await telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "Exit Reason Performance" in msg_mock.call_args_list[0][0][0] assert "Exit Reason Performance" in msg_mock.call_args_list[0][0][0]
assert "`roi\t2.842 USDT (10.00%) (1)`" in msg_mock.call_args_list[0][0][0] assert "`roi\t2.842 USDT (9.47%) (1)`" in msg_mock.call_args_list[0][0][0]
context.args = ["XRP/USDT"] context.args = ["XRP/USDT"]
await telegram._exit_reason_performance(update=update, context=context) await telegram._exit_reason_performance(update=update, context=context)