Merge branch 'develop' into strategy_utils
This commit is contained in:
commit
4d8e3c25bd
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user