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-requests==2.28.11.15
- types-tabulate==0.9.0.1
- types-python-dateutil==2.8.19.9
- SQLAlchemy==2.0.4
- types-python-dateutil==2.8.19.10
- SQLAlchemy==2.0.5.post1
# stages: [push]
- repo: https://github.com/pycqa/isort

View File

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

View File

@ -2,8 +2,7 @@ from datetime import datetime, timezone
from typing import Any, ClassVar, Dict, Optional
from sqlalchemy import String, or_
from sqlalchemy.orm import Mapped, Query, mapped_column
from sqlalchemy.orm.scoping import _QueryDescriptorType
from sqlalchemy.orm import Mapped, Query, QueryPropertyDescriptor, mapped_column
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import ModelBase, SessionType
@ -14,7 +13,7 @@ class PairLock(ModelBase):
Pair Locks database model.
"""
__tablename__ = 'pairlocks'
query: ClassVar[_QueryDescriptorType]
query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType]
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 sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func
from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship
from sqlalchemy.orm.scoping import _QueryDescriptorType
from sqlalchemy.orm import (Mapped, Query, QueryPropertyDescriptor, lazyload, mapped_column,
relationship)
from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES,
BuySell, LongShort)
@ -36,7 +36,7 @@ class Order(ModelBase):
Mirrors CCXT Order structure
"""
__tablename__ = 'orders'
query: ClassVar[_QueryDescriptorType]
query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType]
# Uniqueness should be ensured over pair, order_id
@ -1181,7 +1181,7 @@ class Trade(ModelBase, LocalTrade):
Note: Fields must be aligned with LocalTrade class
"""
__tablename__ = 'trades'
query: ClassVar[_QueryDescriptorType]
query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType]
use_db: bool = True

View File

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

View File

@ -42,7 +42,8 @@ logger = logging.getLogger(__name__)
# 2.22: Add FreqAI to backtesting
# 2.23: Allow plot config request in webserver mode
# 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.
router_public = APIRouter()

View File

@ -192,6 +192,11 @@ class RPC:
current_profit = trade.close_profit or 0.0
current_profit_abs = trade.close_profit_abs or 0.0
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
if not isnan(current_profit_abs) and self._fiat_converter:
@ -224,6 +229,7 @@ class RPC:
total_profit_abs=total_profit_abs,
total_profit_fiat=total_profit_fiat,
total_profit_ratio=total_profit_ratio,
stoploss_current_dist=stoploss_current_dist,
stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8),
stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2),

View File

@ -510,14 +510,14 @@ class Telegram(RPCHandler):
if 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:
lines.append("({})".format(cur_entry_datetime
.humanize(granularity=["day", "hour", "minute"])))
lines.append(f"*Amount:* {cur_entry_amount} "
f"({round_coin_value(order['cost'], quote_currency)})")
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']}")
# TODO: is this really useful?
@ -569,6 +569,8 @@ class Telegram(RPCHandler):
and not o['ft_order_side'] == 'stoploss'])
r['exit_reason'] = r.get('exit_reason', "")
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['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency'])
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`'}"
+ " ` ({leverage}x)`" if r.get('leverage') 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 "",
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
]
if position_adjust:
max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "")
lines.append("*Number of Entries:* `{num_entries}" + max_buy_str + "`")
lines.append("*Number of Exits:* `{num_exits}`")
lines.extend([
"*Number of Entries:* `{num_entries}" + max_buy_str + "`",
"*Number of Exits:* `{num_exits}`"
])
lines.extend([
"*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "",
"*Open Date:* `{open_date}`",
"*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: *")
+ "`{profit_ratio:.2%}` `({profit_abs_r})`",
])
if r['is_open']:
if r.get('realized_profit'):
lines.append(
"*Realized Profit:* `{realized_profit_r} {realized_profit_ratio:.2%}`")
lines.append("*Total Profit:* `{total_profit_abs_r}` ")
lines.extend([
"*Realized Profit:* `{realized_profit_ratio:.2%} ({realized_profit_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']
and r['initial_stop_loss_ratio'] is not None):
# Adding initial stoploss only if it is different from stoploss

View File

@ -7,7 +7,7 @@
-r docs/requirements-docs.txt
coveralls==3.3.1
ruff==0.0.253
ruff==0.0.254
mypy==1.0.1
pre-commit==3.1.1
pytest==7.2.1
@ -29,4 +29,4 @@ types-cachetools==5.3.0.4
types-filelock==3.2.7
types-requests==2.28.11.15
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-ta==0.3.14b
ccxt==2.8.98
cryptography==39.0.1
ccxt==2.9.4
cryptography==39.0.2
aiohttp==3.8.4
SQLAlchemy==2.0.4
SQLAlchemy==2.0.5.post1
python-telegram-bot==13.15
arrow==1.2.3
cachetools==4.2.2
@ -28,7 +28,7 @@ py_find_1st==1.1.5
# Load ticker files 30% faster
python-rapidjson==1.9
# Properly format api responses
orjson==3.8.6
orjson==3.8.7
# Notify systemd
sdnotify==0.3.2
@ -45,7 +45,7 @@ psutil==5.9.4
colorama==0.4.6
# Building config files interactively
questionary==1.10.0
prompt-toolkit==3.0.37
prompt-toolkit==3.0.38
# Extensions to datetime library
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_requested': 91.07468124,
'stake_amount': 0.001,
'max_stake_amount': ANY,
'max_stake_amount': None,
'trade_duration': None,
'trade_duration_s': None,
'close_profit': None,
@ -79,6 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'realized_profit_ratio': None,
'total_profit_abs': -4.09e-06,
'total_profit_fiat': ANY,
'total_profit_ratio': None,
'exchange': 'binance',
'leverage': 1.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()
response = deepcopy(gen_response)
response.update({
'max_stake_amount': 0.001,
'total_profit_ratio': pytest.approx(-0.00409),
})
assert results[0] == response
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_ratio': ANY,
'stoploss_current_dist_pct': ANY,
'max_stake_amount': 0.001,
'profit_ratio': ANY,
'profit_pct': ANY,
'profit_abs': ANY,
'total_profit_abs': ANY,
'total_profit_ratio': ANY,
'current_rate': ANY,
})
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,
'total_profit_abs': ANY,
'total_profit_fiat': ANY,
'total_profit_ratio': ANY,
'realized_profit': 0.0,
'realized_profit_ratio': None,
'current_rate': current_rate,

View File

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