mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Merge branch 'develop' into feat/binance_trades_fast
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -560,7 +560,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish to PyPI (Test)
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
@@ -587,7 +587,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||
|
||||
|
||||
deploy-docker:
|
||||
|
||||
@@ -2,6 +2,6 @@ markdown==3.7
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.5.50
|
||||
mdx_truly_sane_lists==1.3
|
||||
pymdown-extensions==10.14
|
||||
pymdown-extensions==10.14.1
|
||||
jinja2==3.1.5
|
||||
mike==2.1.3
|
||||
|
||||
@@ -444,7 +444,7 @@ def _download_trades_history(
|
||||
if not trades.empty and since < trades.iloc[-1]["timestamp"]:
|
||||
# Reset since to the last available point
|
||||
# - 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(
|
||||
f"Using last trade date -5s - Downloading trades for {pair} "
|
||||
f"since: {format_ms_time(since)}."
|
||||
|
||||
@@ -367,7 +367,7 @@ class Binance(Exchange):
|
||||
return {}
|
||||
|
||||
async def _async_get_trade_history_id_startup(
|
||||
self, pair: str, since: int | None
|
||||
self, pair: str, since: int
|
||||
) -> tuple[list[list], str]:
|
||||
"""
|
||||
override for initial call
|
||||
|
||||
@@ -2960,7 +2960,7 @@ class Exchange:
|
||||
return trades[-1].get("timestamp")
|
||||
|
||||
async def _async_get_trade_history_id_startup(
|
||||
self, pair: str, since: int | None
|
||||
self, pair: str, since: int
|
||||
) -> tuple[list[list], str]:
|
||||
"""
|
||||
override for initial trade_history_id call
|
||||
@@ -2968,7 +2968,7 @@ class Exchange:
|
||||
return await self._async_fetch_trades(pair, since=since)
|
||||
|
||||
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]]:
|
||||
"""
|
||||
Asynchronously gets trade history using fetch_trades
|
||||
@@ -3022,7 +3022,7 @@ class Exchange:
|
||||
return (pair, trades)
|
||||
|
||||
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]]:
|
||||
"""
|
||||
Asynchronously gets trade history using fetch_trades,
|
||||
@@ -3063,7 +3063,7 @@ class Exchange:
|
||||
async def _async_get_trade_history(
|
||||
self,
|
||||
pair: str,
|
||||
since: int | None = None,
|
||||
since: int,
|
||||
until: int | None = None,
|
||||
from_id: str | None = None,
|
||||
) -> tuple[str, list[list]]:
|
||||
@@ -3094,7 +3094,7 @@ class Exchange:
|
||||
def get_historic_trades(
|
||||
self,
|
||||
pair: str,
|
||||
since: int | None = None,
|
||||
since: int,
|
||||
until: int | None = None,
|
||||
from_id: str | None = None,
|
||||
) -> tuple[str, list]:
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from collections import defaultdict
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timezone
|
||||
from math import isclose
|
||||
from typing import Any, ClassVar, Optional, cast
|
||||
|
||||
@@ -1914,19 +1914,20 @@ class Trade(ModelBase, LocalTrade):
|
||||
return total_open_stake_amount or 0
|
||||
|
||||
@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.
|
||||
"""
|
||||
filters: list = [Trade.is_open.is_(False)]
|
||||
if minutes:
|
||||
start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes)
|
||||
filters.append(Trade.close_date >= start_date)
|
||||
|
||||
columns_coal = [func.coalesce(c, fallback).label(c.name) for c in columns]
|
||||
pair_costs = (
|
||||
select(
|
||||
Trade.pair,
|
||||
*columns_coal,
|
||||
func.sum(
|
||||
(
|
||||
func.coalesce(Order.filled, Order.amount)
|
||||
@@ -1939,35 +1940,46 @@ class Trade(ModelBase, LocalTrade):
|
||||
.filter(
|
||||
*filters,
|
||||
Order.ft_order_side == case((Trade.is_short.is_(True), "sell"), else_="buy"),
|
||||
Order.filled > 0,
|
||||
)
|
||||
# Order.filled.gt > 0
|
||||
.group_by(Trade.pair)
|
||||
.group_by(*columns)
|
||||
.cte("pair_costs")
|
||||
)
|
||||
trades_grouped = (
|
||||
select(
|
||||
Trade.pair,
|
||||
*columns_coal,
|
||||
func.sum(Trade.close_profit_abs).label("profit_sum_abs"),
|
||||
func.count(Trade.pair).label("count"),
|
||||
func.count(*columns_coal).label("count"),
|
||||
)
|
||||
.filter(*filters)
|
||||
.group_by(Trade.pair)
|
||||
.group_by(*columns_coal)
|
||||
.cte("trades_grouped")
|
||||
)
|
||||
q = (
|
||||
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(
|
||||
"profit_ratio"
|
||||
),
|
||||
trades_grouped.c.profit_sum_abs,
|
||||
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"))
|
||||
)
|
||||
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 [
|
||||
{
|
||||
"pair": pair,
|
||||
@@ -1992,17 +2004,8 @@ class Trade(ModelBase, LocalTrade):
|
||||
if pair is not None:
|
||||
filters.append(Trade.pair == pair)
|
||||
|
||||
enter_tag_perf = Trade.session.execute(
|
||||
select(
|
||||
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()
|
||||
pair_rates_query = Trade._generic_performance_query([Trade.enter_tag], filters, "Other")
|
||||
enter_tag_perf = Trade.session.execute(pair_rates_query).all()
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -2026,17 +2029,9 @@ class Trade(ModelBase, LocalTrade):
|
||||
filters: list = [Trade.is_open.is_(False)]
|
||||
if pair is not None:
|
||||
filters.append(Trade.pair == pair)
|
||||
sell_tag_perf = Trade.session.execute(
|
||||
select(
|
||||
Trade.exit_reason,
|
||||
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()
|
||||
|
||||
pair_rates_query = Trade._generic_performance_query([Trade.exit_reason], filters, "Other")
|
||||
sell_tag_perf = Trade.session.execute(pair_rates_query).all()
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -2117,13 +2112,9 @@ class Trade(ModelBase, LocalTrade):
|
||||
if start_date:
|
||||
filters.append(Trade.close_date >= start_date)
|
||||
|
||||
best_pair = Trade.session.execute(
|
||||
select(Trade.pair, func.sum(Trade.close_profit).label("profit_sum"))
|
||||
.filter(*filters)
|
||||
.group_by(Trade.pair)
|
||||
.order_by(desc("profit_sum"))
|
||||
).first()
|
||||
|
||||
pair_rates_query = Trade._generic_performance_query([Trade.pair], filters)
|
||||
best_pair = Trade.session.execute(pair_rates_query).first()
|
||||
# returns pair, profit_ratio, abs_profit, count
|
||||
return best_pair
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -3,12 +3,14 @@ Performance pair list filter
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from freqtrade.exchange.exchange_types import Tickers
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
|
||||
from freqtrade.util.datetime_helpers import dt_now
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -69,7 +71,8 @@ class PerformanceFilter(IPairList):
|
||||
"""
|
||||
# Get the trading performance for pairs from database
|
||||
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:
|
||||
# Performancefilter does not work in backtesting.
|
||||
self.log_once("PerformanceFilter is not available in this mode.", logger.warning)
|
||||
|
||||
@@ -150,6 +150,7 @@ class Profit(BaseModel):
|
||||
best_pair: str
|
||||
best_rate: float
|
||||
best_pair_profit_ratio: float
|
||||
best_pair_profit_abs: float
|
||||
winning_trades: int
|
||||
losing_trades: int
|
||||
profit_factor: float
|
||||
|
||||
@@ -665,6 +665,7 @@ class RPC:
|
||||
"best_pair": best_pair[0] if best_pair else "",
|
||||
"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_abs": best_pair[2] if best_pair else 0,
|
||||
"winning_trades": winning_trades,
|
||||
"losing_trades": losing_trades,
|
||||
"profit_factor": profit_factor,
|
||||
|
||||
@@ -1028,6 +1028,7 @@ class Telegram(RPCHandler):
|
||||
avg_duration = stats["avg_duration"]
|
||||
best_pair = stats["best_pair"]
|
||||
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"]
|
||||
expectancy = stats["expectancy"]
|
||||
expectancy_ratio = stats["expectancy_ratio"]
|
||||
@@ -1067,7 +1068,8 @@ class Telegram(RPCHandler):
|
||||
if stats["closed_trade_count"] > 0:
|
||||
markdown_msg += (
|
||||
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"*Profit factor:* `{stats['profit_factor']:.2f}`\n"
|
||||
f"*Max Drawdown:* `{stats['max_drawdown']:.2%} "
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
-r docs/requirements-docs.txt
|
||||
|
||||
coveralls==4.0.1
|
||||
ruff==0.9.2
|
||||
ruff==0.9.3
|
||||
mypy==1.14.1
|
||||
pre-commit==4.0.1
|
||||
pre-commit==4.1.0
|
||||
pytest==8.3.4
|
||||
pytest-asyncio==0.25.2
|
||||
pytest-cov==6.0.0
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
scipy==1.15.1
|
||||
scikit-learn==1.6.1
|
||||
ft-scikit-optimize==0.9.2
|
||||
filelock==3.16.1
|
||||
filelock==3.17.0
|
||||
|
||||
@@ -4,7 +4,7 @@ bottleneck==1.4.2
|
||||
numexpr==2.10.2
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==4.4.49
|
||||
ccxt==4.4.50
|
||||
cryptography==42.0.8; platform_machine == 'armv7l'
|
||||
cryptography==44.0.0; platform_machine != 'armv7l'
|
||||
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 ~
|
||||
httpx>=0.24.1
|
||||
humanize==4.11.0
|
||||
cachetools==5.5.0
|
||||
cachetools==5.5.1
|
||||
requests==2.32.3
|
||||
urllib3==2.3.0
|
||||
jsonschema==4.23.0
|
||||
@@ -38,8 +38,8 @@ orjson==3.10.15
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.115.6
|
||||
pydantic==2.10.5
|
||||
fastapi==0.115.7
|
||||
pydantic==2.10.6
|
||||
uvicorn==0.34.0
|
||||
pyjwt==2.10.1
|
||||
aiofiles==24.1.0
|
||||
@@ -47,7 +47,7 @@ psutil==6.1.1
|
||||
|
||||
# Building config files interactively
|
||||
questionary==2.1.0
|
||||
prompt-toolkit==3.0.36
|
||||
prompt-toolkit==3.0.50
|
||||
# Extensions to datetime library
|
||||
python-dateutil==2.9.0.post0
|
||||
pytz==2024.2
|
||||
|
||||
@@ -1931,9 +1931,9 @@ def test_get_overall_performance(fee):
|
||||
@pytest.mark.parametrize(
|
||||
"is_short,pair,profit",
|
||||
[
|
||||
(True, "ETC/BTC", -0.005),
|
||||
(False, "XRP/BTC", 0.01),
|
||||
(None, "XRP/BTC", 0.01),
|
||||
(True, "XRP/BTC", -0.00018780487),
|
||||
(False, "ETC/BTC", 0.00003860975),
|
||||
(None, "XRP/BTC", 0.000025203252),
|
||||
],
|
||||
)
|
||||
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)
|
||||
res = Trade.get_best_pair()
|
||||
assert len(res) == 2
|
||||
assert len(res) == 4
|
||||
assert res[0] == pair
|
||||
assert res[1] == profit
|
||||
assert pytest.approx(res[1]) == profit
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@@ -1954,9 +1954,9 @@ def test_get_best_pair_lev(fee):
|
||||
|
||||
create_mock_trades_with_leverage(fee)
|
||||
res = Trade.get_best_pair()
|
||||
assert len(res) == 2
|
||||
assert res[0] == "DOGE/BTC"
|
||||
assert res[1] == 0.1713156134055116
|
||||
assert len(res) == 4
|
||||
assert res[0] == "ETC/BTC"
|
||||
assert pytest.approx(res[1]) == 0.00003860975
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
|
||||
@@ -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["latest_trade_humanized"] == "17 minutes ago"
|
||||
assert stats["avg_duration"] in ("0:17:40")
|
||||
assert stats["best_pair"] == "XRP/USDT"
|
||||
assert stats["best_rate"] == 10.0
|
||||
assert stats["best_pair"] == "NEO/USDT"
|
||||
assert stats["best_rate"] == 1.99
|
||||
|
||||
# Test non-available pair
|
||||
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["latest_trade_humanized"] == "17 minutes ago"
|
||||
assert stats["avg_duration"] in ("0:17:40")
|
||||
assert stats["best_pair"] == "XRP/USDT"
|
||||
assert stats["best_rate"] == 10.0
|
||||
assert stats["best_pair"] == "NEO/USDT"
|
||||
assert stats["best_rate"] == 1.99
|
||||
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 res[0]["enter_tag"] == "TEST1"
|
||||
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)
|
||||
|
||||
assert len(res) == 3
|
||||
assert res[0]["enter_tag"] == "TEST1"
|
||||
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):
|
||||
@@ -1041,17 +1041,20 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
|
||||
assert len(res) == 2
|
||||
assert res[0]["enter_tag"] == "TEST1"
|
||||
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]["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
|
||||
res = rpc._rpc_enter_tag_performance("ETC/BTC")
|
||||
assert len(res) == 1
|
||||
assert res[0]["count"] == 1
|
||||
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:
|
||||
@@ -1075,7 +1078,7 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker)
|
||||
assert len(res) == 3
|
||||
assert res[0]["exit_reason"] == "exit_signal"
|
||||
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[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 res[0]["exit_reason"] == "sell_signal"
|
||||
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]["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
|
||||
res = rpc._rpc_exit_reason_performance("ETC/BTC")
|
||||
assert len(res) == 1
|
||||
assert res[0]["count"] == 1
|
||||
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:
|
||||
|
||||
@@ -963,9 +963,10 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
(
|
||||
True,
|
||||
{
|
||||
"best_pair": "ETC/BTC",
|
||||
"best_rate": -0.5,
|
||||
"best_pair_profit_ratio": -0.005,
|
||||
"best_pair": "XRP/BTC",
|
||||
"best_rate": -0.02,
|
||||
"best_pair_profit_ratio": -0.00018780487,
|
||||
"best_pair_profit_abs": -0.001155,
|
||||
"profit_all_coin": 15.382312,
|
||||
"profit_all_fiat": 189894.6470718,
|
||||
"profit_all_percent_mean": 49.62,
|
||||
@@ -994,9 +995,10 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
(
|
||||
False,
|
||||
{
|
||||
"best_pair": "XRP/BTC",
|
||||
"best_rate": 1.0,
|
||||
"best_pair_profit_ratio": 0.01,
|
||||
"best_pair": "ETC/BTC",
|
||||
"best_rate": 0.0,
|
||||
"best_pair_profit_ratio": 0.00003860975,
|
||||
"best_pair_profit_abs": 0.000584127,
|
||||
"profit_all_coin": -15.46546305,
|
||||
"profit_all_fiat": -190921.14135225,
|
||||
"profit_all_percent_mean": -49.62,
|
||||
@@ -1026,8 +1028,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
None,
|
||||
{
|
||||
"best_pair": "XRP/BTC",
|
||||
"best_rate": 1.0,
|
||||
"best_pair_profit_ratio": 0.01,
|
||||
"best_rate": 0.0,
|
||||
"best_pair_profit_ratio": 0.000025203252,
|
||||
"best_pair_profit_abs": 0.000155,
|
||||
"profit_all_coin": -14.87167525,
|
||||
"profit_all_fiat": -183590.83096125,
|
||||
"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() == {
|
||||
"avg_duration": ANY,
|
||||
"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"],
|
||||
"first_trade_date": ANY,
|
||||
"first_trade_humanized": ANY,
|
||||
@@ -1206,7 +1210,8 @@ def test_api_entries(botclient, fee):
|
||||
resp = response[0]
|
||||
assert resp["enter_tag"] == "TEST1"
|
||||
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):
|
||||
@@ -1225,7 +1230,8 @@ def test_api_exits(botclient, fee):
|
||||
resp = response[0]
|
||||
assert resp["exit_reason"] == "sell_signal"
|
||||
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):
|
||||
|
||||
@@ -918,7 +918,7 @@ async def test_telegram_profit_handle(
|
||||
)
|
||||
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 "*Profit factor:*" 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)
|
||||
assert msg_mock.call_count == 1
|
||||
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"]
|
||||
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)
|
||||
assert msg_mock.call_count == 1
|
||||
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"]
|
||||
|
||||
await telegram._exit_reason_performance(update=update, context=context)
|
||||
|
||||
Reference in New Issue
Block a user