Merge remote-tracking branch 'origin/strategy_utils' into strategy_utils

# Conflicts:
#	freqtrade/commands/strategy_utils_commands.py
#	tests/test_strategy_updater.py
This commit is contained in:
hippocritical 2023-03-10 09:00:00 +01:00
commit 5f8202e1b5
13 changed files with 52 additions and 28 deletions

View File

@ -17,8 +17,8 @@ repos:
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.11.15 - types-requests==2.28.11.15
- types-tabulate==0.9.0.1 - types-tabulate==0.9.0.1
- types-python-dateutil==2.8.19.9 - types-python-dateutil==2.8.19.10
- SQLAlchemy==2.0.4 - SQLAlchemy==2.0.5.post1
# stages: [push] # stages: [push]
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.4.2 mkdocs==1.4.2
mkdocs-material==9.0.15 mkdocs-material==9.1.1
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.9.2 pymdown-extensions==9.10
jinja2==3.1.2 jinja2==3.1.2

View File

@ -2,8 +2,7 @@ from datetime import datetime, timezone
from typing import Any, ClassVar, Dict, Optional from typing import Any, ClassVar, Dict, Optional
from sqlalchemy import String, or_ from sqlalchemy import String, or_
from sqlalchemy.orm import Mapped, Query, mapped_column from sqlalchemy.orm import Mapped, Query, QueryPropertyDescriptor, mapped_column
from sqlalchemy.orm.scoping import _QueryDescriptorType
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.persistence.base import ModelBase, SessionType
@ -14,7 +13,7 @@ class PairLock(ModelBase):
Pair Locks database model. Pair Locks database model.
""" """
__tablename__ = 'pairlocks' __tablename__ = 'pairlocks'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)

View File

@ -8,8 +8,8 @@ from math import isclose
from typing import Any, ClassVar, Dict, List, Optional, cast from typing import Any, ClassVar, Dict, List, Optional, cast
from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func
from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship from sqlalchemy.orm import (Mapped, Query, QueryPropertyDescriptor, lazyload, mapped_column,
from sqlalchemy.orm.scoping import _QueryDescriptorType relationship)
from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES,
BuySell, LongShort) BuySell, LongShort)
@ -36,7 +36,7 @@ class Order(ModelBase):
Mirrors CCXT Order structure Mirrors CCXT Order structure
""" """
__tablename__ = 'orders' __tablename__ = 'orders'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
# Uniqueness should be ensured over pair, order_id # Uniqueness should be ensured over pair, order_id
@ -1181,7 +1181,7 @@ class Trade(ModelBase, LocalTrade):
Note: Fields must be aligned with LocalTrade class Note: Fields must be aligned with LocalTrade class
""" """
__tablename__ = 'trades' __tablename__ = 'trades'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
use_db: bool = True use_db: bool = True

View File

@ -286,6 +286,7 @@ class OpenTradeSchema(TradeSchema):
current_rate: float current_rate: float
total_profit_abs: float total_profit_abs: float
total_profit_fiat: Optional[float] total_profit_fiat: Optional[float]
total_profit_ratio: Optional[float]
open_order: Optional[str] open_order: Optional[str]

View File

@ -42,7 +42,8 @@ logger = logging.getLogger(__name__)
# 2.22: Add FreqAI to backtesting # 2.22: Add FreqAI to backtesting
# 2.23: Allow plot config request in webserver mode # 2.23: Allow plot config request in webserver mode
# 2.24: Add cancel_open_order endpoint # 2.24: Add cancel_open_order endpoint
API_VERSION = 2.24 # 2.25: Add several profit values to /status endpoint
API_VERSION = 2.25
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()

View File

@ -192,6 +192,11 @@ class RPC:
current_profit = trade.close_profit or 0.0 current_profit = trade.close_profit or 0.0
current_profit_abs = trade.close_profit_abs or 0.0 current_profit_abs = trade.close_profit_abs or 0.0
total_profit_abs = trade.realized_profit + current_profit_abs total_profit_abs = trade.realized_profit + current_profit_abs
total_profit_ratio: Optional[float] = None
if trade.max_stake_amount:
total_profit_ratio = (
(total_profit_abs / trade.max_stake_amount) * trade.leverage
)
# Calculate fiat profit # Calculate fiat profit
if not isnan(current_profit_abs) and self._fiat_converter: if not isnan(current_profit_abs) and self._fiat_converter:
@ -224,6 +229,7 @@ class RPC:
total_profit_abs=total_profit_abs, total_profit_abs=total_profit_abs,
total_profit_fiat=total_profit_fiat, total_profit_fiat=total_profit_fiat,
total_profit_ratio=total_profit_ratio,
stoploss_current_dist=stoploss_current_dist, stoploss_current_dist=stoploss_current_dist,
stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8),
stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2),

View File

@ -510,14 +510,14 @@ class Telegram(RPCHandler):
if prev_avg_price: if prev_avg_price:
minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price
lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg profit") lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg Profit")
if is_open: if is_open:
lines.append("({})".format(cur_entry_datetime lines.append("({})".format(cur_entry_datetime
.humanize(granularity=["day", "hour", "minute"]))) .humanize(granularity=["day", "hour", "minute"])))
lines.append(f"*Amount:* {cur_entry_amount} " lines.append(f"*Amount:* {cur_entry_amount} "
f"({round_coin_value(order['cost'], quote_currency)})") f"({round_coin_value(order['cost'], quote_currency)})")
lines.append(f"*Average {wording} Price:* {cur_entry_average} " lines.append(f"*Average {wording} Price:* {cur_entry_average} "
f"({price_to_1st_entry:.2%} from 1st entry rate)") f"({price_to_1st_entry:.2%} from 1st entry Rate)")
lines.append(f"*Order filled:* {order['order_filled_date']}") lines.append(f"*Order filled:* {order['order_filled_date']}")
# TODO: is this really useful? # TODO: is this really useful?
@ -569,6 +569,8 @@ class Telegram(RPCHandler):
and not o['ft_order_side'] == 'stoploss']) and not o['ft_order_side'] == 'stoploss'])
r['exit_reason'] = r.get('exit_reason', "") r['exit_reason'] = r.get('exit_reason', "")
r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency'])
r['max_stake_amount_r'] = round_coin_value(
r['max_stake_amount'] or r['stake_amount'], r['quote_currency'])
r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency'])
r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency']) r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency'])
r['total_profit_abs_r'] = round_coin_value( r['total_profit_abs_r'] = round_coin_value(
@ -580,31 +582,37 @@ class Telegram(RPCHandler):
f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}"
+ " ` ({leverage}x)`" if r.get('leverage') else "", + " ` ({leverage}x)`" if r.get('leverage') else "",
"*Amount:* `{amount} ({stake_amount_r})`", "*Amount:* `{amount} ({stake_amount_r})`",
"*Total invested:* `{max_stake_amount_r}`" if position_adjust else "",
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
] ]
if position_adjust: if position_adjust:
max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "") max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "")
lines.append("*Number of Entries:* `{num_entries}" + max_buy_str + "`") lines.extend([
lines.append("*Number of Exits:* `{num_exits}`") "*Number of Entries:* `{num_entries}" + max_buy_str + "`",
"*Number of Exits:* `{num_exits}`"
])
lines.extend([ lines.extend([
"*Open Rate:* `{open_rate:.8f}`", "*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "", "*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "",
"*Open Date:* `{open_date}`", "*Open Date:* `{open_date}`",
"*Close Date:* `{close_date}`" if r['close_date'] else "", "*Close Date:* `{close_date}`" if r['close_date'] else "",
"*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", " \n*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "",
("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *") ("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *")
+ "`{profit_ratio:.2%}` `({profit_abs_r})`", + "`{profit_ratio:.2%}` `({profit_abs_r})`",
]) ])
if r['is_open']: if r['is_open']:
if r.get('realized_profit'): if r.get('realized_profit'):
lines.append( lines.extend([
"*Realized Profit:* `{realized_profit_r} {realized_profit_ratio:.2%}`") "*Realized Profit:* `{realized_profit_ratio:.2%} ({realized_profit_r})`",
lines.append("*Total Profit:* `{total_profit_abs_r}` ") "*Total Profit:* `{total_profit_ratio:.2%} ({total_profit_abs_r})`"
])
# Append empty line to improve readability
lines.append(" ")
if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] if (r['stop_loss_abs'] != r['initial_stop_loss_abs']
and r['initial_stop_loss_ratio'] is not None): and r['initial_stop_loss_ratio'] is not None):
# Adding initial stoploss only if it is different from stoploss # Adding initial stoploss only if it is different from stoploss

View File

@ -7,7 +7,7 @@
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
coveralls==3.3.1 coveralls==3.3.1
ruff==0.0.253 ruff==0.0.254
mypy==1.0.1 mypy==1.0.1
pre-commit==3.1.1 pre-commit==3.1.1
pytest==7.2.1 pytest==7.2.1
@ -29,4 +29,4 @@ types-cachetools==5.3.0.4
types-filelock==3.2.7 types-filelock==3.2.7
types-requests==2.28.11.15 types-requests==2.28.11.15
types-tabulate==0.9.0.1 types-tabulate==0.9.0.1
types-python-dateutil==2.8.19.9 types-python-dateutil==2.8.19.10

View File

@ -2,10 +2,10 @@ numpy==1.24.2
pandas==1.5.3 pandas==1.5.3
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.8.98 ccxt==2.9.4
cryptography==39.0.1 cryptography==39.0.2
aiohttp==3.8.4 aiohttp==3.8.4
SQLAlchemy==2.0.4 SQLAlchemy==2.0.5.post1
python-telegram-bot==13.15 python-telegram-bot==13.15
arrow==1.2.3 arrow==1.2.3
cachetools==4.2.2 cachetools==4.2.2
@ -28,7 +28,7 @@ py_find_1st==1.1.5
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.9 python-rapidjson==1.9
# Properly format api responses # Properly format api responses
orjson==3.8.6 orjson==3.8.7
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2
@ -45,7 +45,7 @@ psutil==5.9.4
colorama==0.4.6 colorama==0.4.6
# Building config files interactively # Building config files interactively
questionary==1.10.0 questionary==1.10.0
prompt-toolkit==3.0.37 prompt-toolkit==3.0.38
# Extensions to datetime library # Extensions to datetime library
python-dateutil==2.8.2 python-dateutil==2.8.2

View File

@ -50,7 +50,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'amount': 91.07468123, 'amount': 91.07468123,
'amount_requested': 91.07468124, 'amount_requested': 91.07468124,
'stake_amount': 0.001, 'stake_amount': 0.001,
'max_stake_amount': ANY, 'max_stake_amount': None,
'trade_duration': None, 'trade_duration': None,
'trade_duration_s': None, 'trade_duration_s': None,
'close_profit': None, 'close_profit': None,
@ -79,6 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'realized_profit_ratio': None, 'realized_profit_ratio': None,
'total_profit_abs': -4.09e-06, 'total_profit_abs': -4.09e-06,
'total_profit_fiat': ANY, 'total_profit_fiat': ANY,
'total_profit_ratio': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0, 'leverage': 1.0,
'interest_rate': 0.0, 'interest_rate': 0.0,
@ -168,6 +169,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
results = rpc._rpc_trade_status() results = rpc._rpc_trade_status()
response = deepcopy(gen_response) response = deepcopy(gen_response)
response.update({
'max_stake_amount': 0.001,
'total_profit_ratio': pytest.approx(-0.00409),
})
assert results[0] == response assert results[0] == response
mocker.patch(f'{EXMS}.get_rate', mocker.patch(f'{EXMS}.get_rate',
@ -181,10 +186,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_current_dist': ANY, 'stoploss_current_dist': ANY,
'stoploss_current_dist_ratio': ANY, 'stoploss_current_dist_ratio': ANY,
'stoploss_current_dist_pct': ANY, 'stoploss_current_dist_pct': ANY,
'max_stake_amount': 0.001,
'profit_ratio': ANY, 'profit_ratio': ANY,
'profit_pct': ANY, 'profit_pct': ANY,
'profit_abs': ANY, 'profit_abs': ANY,
'total_profit_abs': ANY, 'total_profit_abs': ANY,
'total_profit_ratio': ANY,
'current_rate': ANY, 'current_rate': ANY,
}) })
assert results[0] == response_norate assert results[0] == response_norate

View File

@ -1012,6 +1012,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'profit_fiat': ANY, 'profit_fiat': ANY,
'total_profit_abs': ANY, 'total_profit_abs': ANY,
'total_profit_fiat': ANY, 'total_profit_fiat': ANY,
'total_profit_ratio': ANY,
'realized_profit': 0.0, 'realized_profit': 0.0,
'realized_profit_ratio': None, 'realized_profit_ratio': None,
'current_rate': current_rate, 'current_rate': current_rate,

View File

@ -198,6 +198,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'current_rate': 1.098e-05, 'current_rate': 1.098e-05,
'amount': 90.99181074, 'amount': 90.99181074,
'stake_amount': 90.99181074, 'stake_amount': 90.99181074,
'max_stake_amount': 90.99181074,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None, 'enter_tag': None,
'close_profit_ratio': None, 'close_profit_ratio': None,