Merge branch 'develop' into feature_keyval_storage

This commit is contained in:
eSeR1805 2022-06-13 20:05:45 +03:00
commit 4c6074062c
No known key found for this signature in database
GPG Key ID: BA53686259B46936
31 changed files with 732 additions and 841 deletions

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@ -127,7 +127,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@ -211,7 +211,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@ -263,7 +263,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"
@ -282,7 +282,7 @@ jobs:
./tests/test_docs.sh ./tests/test_docs.sh
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"
@ -336,7 +336,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v3 uses: actions/setup-python@v4
with: with:
python-version: "3.9" python-version: "3.9"

View File

@ -14,7 +14,7 @@ repos:
exclude: build_helpers exclude: build_helpers
additional_dependencies: additional_dependencies:
- types-cachetools==5.0.1 - types-cachetools==5.0.1
- types-filelock==3.2.6 - types-filelock==3.2.7
- types-requests==2.27.30 - types-requests==2.27.30
- types-tabulate==0.8.9 - types-tabulate==0.8.9
- types-python-dateutil==2.8.17 - types-python-dateutil==2.8.17

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,5 +1,5 @@
mkdocs==1.3.0 mkdocs==1.3.0
mkdocs-material==8.3.2 mkdocs-material==8.3.4
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2
pymdown-extensions==9.4 pymdown-extensions==9.5
jinja2==3.1.2 jinja2==3.1.2

View File

@ -89,11 +89,12 @@ WHERE id=31;
If you'd still like to remove a trade from the database directly, you can use the below query. If you'd still like to remove a trade from the database directly, you can use the below query.
```sql !!! Danger
DELETE FROM trades WHERE id = <tradeid>; Some systems (Ubuntu) disable foreign keys in their sqlite3 packaging. When using sqlite - please ensure that foreign keys are on by running `PRAGMA foreign_keys = ON` before the above query.
```
```sql ```sql
DELETE FROM trades WHERE id = <tradeid>;
DELETE FROM trades WHERE id = 31; DELETE FROM trades WHERE id = 31;
``` ```
@ -102,13 +103,20 @@ DELETE FROM trades WHERE id = 31;
## Use a different database system ## Use a different database system
Freqtrade is using SQLAlchemy, which supports multiple different database systems. As such, a multitude of database systems should be supported.
Freqtrade does not depend or install any additional database driver. Please refer to the [SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls) on installation instructions for the respective database systems.
The following systems have been tested and are known to work with freqtrade:
* sqlite (default)
* PostgreSQL)
* MariaDB
!!! Warning !!! Warning
By using one of the below database systems, you acknowledge that you know how to manage such a system. Freqtrade will not provide any support with setup or maintenance (or backups) of the below database systems. By using one of the below database systems, you acknowledge that you know how to manage such a system. The freqtrade team will not provide any support with setup or maintenance (or backups) of the below database systems.
### PostgreSQL ### PostgreSQL
Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems.
Installation: Installation:
`pip install psycopg2-binary` `pip install psycopg2-binary`

View File

@ -551,6 +551,7 @@ class AwesomeStrategy(IStrategy):
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (base) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
@ -600,6 +601,7 @@ class AwesomeStrategy(IStrategy):
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in base currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',

View File

@ -328,11 +328,11 @@ Per default `/daily` will return the 7 last days. The example below if for `/dai
> **Daily Profit over the last 3 days:** > **Daily Profit over the last 3 days:**
``` ```
Day Profit BTC Profit USD Day (count) USDT USD Profit %
---------- -------------- ------------ -------------- ------------ ---------- ----------
2018-01-03 0.00224175 BTC 29,142 USD 2022-06-11 (1) -0.746 USDT -0.75 USD -0.08%
2018-01-02 0.00033131 BTC 4,307 USD 2022-06-10 (0) 0 USDT 0.00 USD 0.00%
2018-01-01 0.00269130 BTC 34.986 USD 2022-06-09 (5) 20 USDT 20.10 USD 5.00%
``` ```
### /weekly <n> ### /weekly <n>
@ -342,11 +342,11 @@ from Monday. The example below if for `/weekly 3`:
> **Weekly Profit over the last 3 weeks (starting from Monday):** > **Weekly Profit over the last 3 weeks (starting from Monday):**
``` ```
Monday Profit BTC Profit USD Monday (count) Profit BTC Profit USD Profit %
---------- -------------- ------------ ------------- -------------- ------------ ----------
2018-01-03 0.00224175 BTC 29,142 USD 2018-01-03 (5) 0.00224175 BTC 29,142 USD 4.98%
2017-12-27 0.00033131 BTC 4,307 USD 2017-12-27 (1) 0.00033131 BTC 4,307 USD 0.00%
2017-12-20 0.00269130 BTC 34.986 USD 2017-12-20 (4) 0.00269130 BTC 34.986 USD 5.12%
``` ```
### /monthly <n> ### /monthly <n>
@ -356,11 +356,11 @@ if for `/monthly 3`:
> **Monthly Profit over the last 3 months:** > **Monthly Profit over the last 3 months:**
``` ```
Month Profit BTC Profit USD Month (count) Profit BTC Profit USD Profit %
---------- -------------- ------------ ------------- -------------- ------------ ----------
2018-01 0.00224175 BTC 29,142 USD 2018-01 (20) 0.00224175 BTC 29,142 USD 4.98%
2017-12 0.00033131 BTC 4,307 USD 2017-12 (5) 0.00033131 BTC 4,307 USD 0.00%
2017-11 0.00269130 BTC 34.986 USD 2017-11 (10) 0.00269130 BTC 34.986 USD 5.10%
``` ```
### /whitelist ### /whitelist

View File

@ -239,3 +239,52 @@ Possible parameters are:
The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format. The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
The only possible value here is `{status}`. The only possible value here is `{status}`.
## Discord
A special form of webhooks is available for discord.
You can configure this as follows:
```json
"discord": {
"enabled": true,
"webhook_url": "https://discord.com/api/webhooks/<Your webhook URL ...>",
"exit_fill": [
{"Trade ID": "{trade_id}"},
{"Exchange": "{exchange}"},
{"Pair": "{pair}"},
{"Direction": "{direction}"},
{"Open rate": "{open_rate}"},
{"Close rate": "{close_rate}"},
{"Amount": "{amount}"},
{"Open date": "{open_date:%Y-%m-%d %H:%M:%S}"},
{"Close date": "{close_date:%Y-%m-%d %H:%M:%S}"},
{"Profit": "{profit_amount} {stake_currency}"},
{"Profitability": "{profit_ratio:.2%}"},
{"Enter tag": "{enter_tag}"},
{"Exit Reason": "{exit_reason}"},
{"Strategy": "{strategy}"},
{"Timeframe": "{timeframe}"},
],
"entry_fill": [
{"Trade ID": "{trade_id}"},
{"Exchange": "{exchange}"},
{"Pair": "{pair}"},
{"Direction": "{direction}"},
{"Open rate": "{open_rate}"},
{"Amount": "{amount}"},
{"Open date": "{open_date:%Y-%m-%d %H:%M:%S}"},
{"Enter tag": "{enter_tag}"},
{"Strategy": "{strategy} {timeframe}"},
]
}
```
The above represents the default (`exit_fill` and `entry_fill` are optional and will default to the above configuration) - modifications are obviously possible.
Available fields correspond to the fields for webhooks and are documented in the corresponding webhook sections.
The notifications will look as follows by default.
![discord-notification](assets/discord_notification.png)

View File

@ -336,6 +336,47 @@ CONF_SCHEMA = {
'webhookstatus': {'type': 'object'}, 'webhookstatus': {'type': 'object'},
}, },
}, },
'discord': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'webhook_url': {'type': 'string'},
"exit_fill": {
'type': 'array', 'items': {'type': 'object'},
'default': [
{"Trade ID": "{trade_id}"},
{"Exchange": "{exchange}"},
{"Pair": "{pair}"},
{"Direction": "{direction}"},
{"Open rate": "{open_rate}"},
{"Close rate": "{close_rate}"},
{"Amount": "{amount}"},
{"Open date": "{open_date:%Y-%m-%d %H:%M:%S}"},
{"Close date": "{close_date:%Y-%m-%d %H:%M:%S}"},
{"Profit": "{profit_amount} {stake_currency}"},
{"Profitability": "{profit_ratio:.2%}"},
{"Enter tag": "{enter_tag}"},
{"Exit Reason": "{exit_reason}"},
{"Strategy": "{strategy}"},
{"Timeframe": "{timeframe}"},
]
},
"entry_fill": {
'type': 'array', 'items': {'type': 'object'},
'default': [
{"Trade ID": "{trade_id}"},
{"Exchange": "{exchange}"},
{"Pair": "{pair}"},
{"Direction": "{direction}"},
{"Open rate": "{open_rate}"},
{"Amount": "{amount}"},
{"Open date": "{open_date:%Y-%m-%d %H:%M:%S}"},
{"Enter tag": "{enter_tag}"},
{"Strategy": "{strategy} {timeframe}"},
]
},
}
},
'api_server': { 'api_server': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {

View File

@ -429,7 +429,7 @@ class Hyperopt:
return new_list return new_list
i = 0 i = 0
asked_non_tried: List[List[Any]] = [] asked_non_tried: List[List[Any]] = []
is_random: List[bool] = [] is_random_non_tried: List[bool] = []
while i < 5 and len(asked_non_tried) < n_points: while i < 5 and len(asked_non_tried) < n_points:
if i < 3: if i < 3:
self.opt.cache_ = {} self.opt.cache_ = {}
@ -438,7 +438,7 @@ class Hyperopt:
else: else:
asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5)) asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5))
is_random = [True for _ in range(len(asked))] is_random = [True for _ in range(len(asked))]
is_random += [rand for x, rand in zip(asked, is_random) is_random_non_tried += [rand for x, rand in zip(asked, is_random)
if x not in self.opt.Xi if x not in self.opt.Xi
and x not in asked_non_tried] and x not in asked_non_tried]
asked_non_tried += [x for x in asked asked_non_tried += [x for x in asked
@ -449,7 +449,7 @@ class Hyperopt:
if asked_non_tried: if asked_non_tried:
return ( return (
asked_non_tried[:min(len(asked_non_tried), n_points)], asked_non_tried[:min(len(asked_non_tried), n_points)],
is_random[:min(len(asked_non_tried), n_points)] is_random_non_tried[:min(len(asked_non_tried), n_points)]
) )
else: else:
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)] return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]

View File

@ -76,7 +76,7 @@ class Order(_DECL_BASE):
@property @property
def safe_filled(self) -> float: def safe_filled(self) -> float:
return self.filled or self.amount or 0.0 return self.filled if self.filled is not None else self.amount or 0.0
@property @property
def safe_fee_base(self) -> float: def safe_fee_base(self) -> float:
@ -850,8 +850,6 @@ class LocalTrade():
tmp_amount = o.safe_amount_after_fee tmp_amount = o.safe_amount_after_fee
tmp_price = o.average or o.price tmp_price = o.average or o.price
if o.filled is not None:
tmp_amount = o.filled
if tmp_amount > 0.0 and tmp_price is not None: if tmp_amount > 0.0 and tmp_price is not None:
total_amount += tmp_amount total_amount += tmp_amount
total_stake += tmp_price * tmp_amount total_stake += tmp_price * tmp_amount

View File

@ -120,6 +120,8 @@ class Stats(BaseModel):
class DailyRecord(BaseModel): class DailyRecord(BaseModel):
date: date date: date
abs_profit: float abs_profit: float
rel_profit: float
starting_balance: float
fiat_value: float fiat_value: float
trade_count: int trade_count: int

View File

@ -36,7 +36,8 @@ logger = logging.getLogger(__name__)
# versions 2.xx -> futures/short branch # versions 2.xx -> futures/short branch
# 2.14: Add entry/exit orders to trade response # 2.14: Add entry/exit orders to trade response
# 2.15: Add backtest history endpoints # 2.15: Add backtest history endpoints
API_VERSION = 2.15 # 2.16: Additional daily metrics
API_VERSION = 2.16
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -86,7 +87,7 @@ def stats(rpc: RPC = Depends(get_rpc)):
@router.get('/daily', response_model=Daily, tags=['info']) @router.get('/daily', response_model=Daily, tags=['info'])
def daily(timescale: int = 7, rpc: RPC = Depends(get_rpc), config=Depends(get_config)): def daily(timescale: int = 7, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_daily_profit(timescale, config['stake_currency'], return rpc._rpc_timeunit_profit(timescale, config['stake_currency'],
config.get('fiat_display_currency', '')) config.get('fiat_display_currency', ''))

59
freqtrade/rpc/discord.py Normal file
View File

@ -0,0 +1,59 @@
import logging
from typing import Any, Dict
from freqtrade.enums.rpcmessagetype import RPCMessageType
from freqtrade.rpc import RPC
from freqtrade.rpc.webhook import Webhook
logger = logging.getLogger(__name__)
class Discord(Webhook):
def __init__(self, rpc: 'RPC', config: Dict[str, Any]):
# super().__init__(rpc, config)
self.rpc = rpc
self.config = config
self.strategy = config.get('strategy', '')
self.timeframe = config.get('timeframe', '')
self._url = self.config['discord']['webhook_url']
self._format = 'json'
self._retries = 1
self._retry_delay = 0.1
def cleanup(self) -> None:
"""
Cleanup pending module resources.
This will do nothing for webhooks, they will simply not be called anymore
"""
pass
def send_msg(self, msg) -> None:
logger.info(f"Sending discord message: {msg}")
if msg['type'].value in self.config['discord']:
msg['strategy'] = self.strategy
msg['timeframe'] = self.timeframe
fields = self.config['discord'].get(msg['type'].value)
color = 0x0000FF
if msg['type'] in (RPCMessageType.EXIT, RPCMessageType.EXIT_FILL):
profit_ratio = msg.get('profit_ratio')
color = (0x00FF00 if profit_ratio > 0 else 0xFF0000)
embeds = [{
'title': f"Trade: {msg['pair']} {msg['type'].value}",
'color': color,
'fields': [],
}]
for f in fields:
for k, v in f.items():
v = v.format(**msg)
embeds[0]['fields'].append( # type: ignore
{'name': k, 'value': v, 'inline': True})
# Send the message to discord channel
payload = {'embeds': embeds}
self._send_msg(payload)

View File

@ -283,33 +283,57 @@ class RPC:
columns.append('# Entries') columns.append('# Entries')
return trades_list, columns, fiat_profit_sum return trades_list, columns, fiat_profit_sum
def _rpc_daily_profit( def _rpc_timeunit_profit(
self, timescale: int, self, timescale: int,
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]: stake_currency: str, fiat_display_currency: str,
today = datetime.now(timezone.utc).date() timeunit: str = 'days') -> Dict[str, Any]:
profit_days: Dict[date, Dict] = {} """
:param timeunit: Valid entries are 'days', 'weeks', 'months'
"""
start_date = datetime.now(timezone.utc).date()
if timeunit == 'weeks':
# weekly
start_date = start_date - timedelta(days=start_date.weekday()) # Monday
if timeunit == 'months':
start_date = start_date.replace(day=1)
def time_offset(step: int):
if timeunit == 'months':
return relativedelta(months=step)
return timedelta(**{timeunit: step})
if not (isinstance(timescale, int) and timescale > 0): if not (isinstance(timescale, int) and timescale > 0):
raise RPCException('timescale must be an integer greater than 0') raise RPCException('timescale must be an integer greater than 0')
profit_units: Dict[date, Dict] = {}
daily_stake = self._freqtrade.wallets.get_total_stake_amount()
for day in range(0, timescale): for day in range(0, timescale):
profitday = today - timedelta(days=day) profitday = start_date - time_offset(day)
trades = Trade.get_trades(trade_filter=[ # Only query for necessary columns for performance reasons.
trades = Trade.query.session.query(Trade.close_profit_abs).filter(
Trade.is_open.is_(False), Trade.is_open.is_(False),
Trade.close_date >= profitday, Trade.close_date >= profitday,
Trade.close_date < (profitday + timedelta(days=1)) Trade.close_date < (profitday + time_offset(1))
]).order_by(Trade.close_date).all() ).order_by(Trade.close_date).all()
curdayprofit = sum( curdayprofit = sum(
trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None) trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None)
profit_days[profitday] = { # Calculate this periods starting balance
daily_stake = daily_stake - curdayprofit
profit_units[profitday] = {
'amount': curdayprofit, 'amount': curdayprofit,
'trades': len(trades) 'daily_stake': daily_stake,
'rel_profit': round(curdayprofit / daily_stake, 8) if daily_stake > 0 else 0,
'trades': len(trades),
} }
data = [ data = [
{ {
'date': key, 'date': f"{key.year}-{key.month:02d}" if timeunit == 'months' else key,
'abs_profit': value["amount"], 'abs_profit': value["amount"],
'starting_balance': value["daily_stake"],
'rel_profit': value["rel_profit"],
'fiat_value': self._fiat_converter.convert_amount( 'fiat_value': self._fiat_converter.convert_amount(
value['amount'], value['amount'],
stake_currency, stake_currency,
@ -317,92 +341,7 @@ class RPC:
) if self._fiat_converter else 0, ) if self._fiat_converter else 0,
'trade_count': value["trades"], 'trade_count': value["trades"],
} }
for key, value in profit_days.items() for key, value in profit_units.items()
]
return {
'stake_currency': stake_currency,
'fiat_display_currency': fiat_display_currency,
'data': data
}
def _rpc_weekly_profit(
self, timescale: int,
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
today = datetime.now(timezone.utc).date()
first_iso_day_of_week = today - timedelta(days=today.weekday()) # Monday
profit_weeks: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0):
raise RPCException('timescale must be an integer greater than 0')
for week in range(0, timescale):
profitweek = first_iso_day_of_week - timedelta(weeks=week)
trades = Trade.get_trades(trade_filter=[
Trade.is_open.is_(False),
Trade.close_date >= profitweek,
Trade.close_date < (profitweek + timedelta(weeks=1))
]).order_by(Trade.close_date).all()
curweekprofit = sum(
trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None)
profit_weeks[profitweek] = {
'amount': curweekprofit,
'trades': len(trades)
}
data = [
{
'date': key,
'abs_profit': value["amount"],
'fiat_value': self._fiat_converter.convert_amount(
value['amount'],
stake_currency,
fiat_display_currency
) if self._fiat_converter else 0,
'trade_count': value["trades"],
}
for key, value in profit_weeks.items()
]
return {
'stake_currency': stake_currency,
'fiat_display_currency': fiat_display_currency,
'data': data
}
def _rpc_monthly_profit(
self, timescale: int,
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
first_day_of_month = datetime.now(timezone.utc).date().replace(day=1)
profit_months: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0):
raise RPCException('timescale must be an integer greater than 0')
for month in range(0, timescale):
profitmonth = first_day_of_month - relativedelta(months=month)
trades = Trade.get_trades(trade_filter=[
Trade.is_open.is_(False),
Trade.close_date >= profitmonth,
Trade.close_date < (profitmonth + relativedelta(months=1))
]).order_by(Trade.close_date).all()
curmonthprofit = sum(
trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None)
profit_months[profitmonth] = {
'amount': curmonthprofit,
'trades': len(trades)
}
data = [
{
'date': f"{key.year}-{key.month:02d}",
'abs_profit': value["amount"],
'fiat_value': self._fiat_converter.convert_amount(
value['amount'],
stake_currency,
fiat_display_currency
) if self._fiat_converter else 0,
'trade_count': value["trades"],
}
for key, value in profit_months.items()
] ]
return { return {
'stake_currency': stake_currency, 'stake_currency': stake_currency,

View File

@ -27,6 +27,12 @@ class RPCManager:
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(self._rpc, config)) self.registered_modules.append(Telegram(self._rpc, config))
# Enable discord
if config.get('discord', {}).get('enabled', False):
logger.info('Enabling rpc.discord ...')
from freqtrade.rpc.discord import Discord
self.registered_modules.append(Discord(self._rpc, config))
# Enable Webhook # Enable Webhook
if config.get('webhook', {}).get('enabled', False): if config.get('webhook', {}).get('enabled', False):
logger.info('Enabling rpc.webhook ...') logger.info('Enabling rpc.webhook ...')

View File

@ -6,6 +6,7 @@ This module manage Telegram communication
import json import json
import logging import logging
import re import re
from dataclasses import dataclass
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from functools import partial from functools import partial
from html import escape from html import escape
@ -37,6 +38,15 @@ logger.debug('Included module rpc.telegram ...')
MAX_TELEGRAM_MESSAGE_LENGTH = 4096 MAX_TELEGRAM_MESSAGE_LENGTH = 4096
@dataclass
class TimeunitMappings:
header: str
message: str
message2: str
callback: str
default: int
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
""" """
Decorator to check if the message comes from the correct chat_id Decorator to check if the message comes from the correct chat_id
@ -564,6 +574,60 @@ class Telegram(RPCHandler):
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
@authorized_only
def _timeunit_stats(self, update: Update, context: CallbackContext, unit: str) -> None:
"""
Handler for /daily <n>
Returns a daily profit (in BTC) over the last n days.
:param bot: telegram bot
:param update: message update
:return: None
"""
vals = {
'days': TimeunitMappings('Day', 'Daily', 'days', 'update_daily', 7),
'weeks': TimeunitMappings('Monday', 'Weekly', 'weeks (starting from Monday)',
'update_weekly', 8),
'months': TimeunitMappings('Month', 'Monthly', 'months', 'update_monthly', 6),
}
val = vals[unit]
stake_cur = self._config['stake_currency']
fiat_disp_cur = self._config.get('fiat_display_currency', '')
try:
timescale = int(context.args[0]) if context.args else val.default
except (TypeError, ValueError, IndexError):
timescale = val.default
try:
stats = self._rpc._rpc_timeunit_profit(
timescale,
stake_cur,
fiat_disp_cur,
unit
)
stats_tab = tabulate(
[[f"{period['date']} ({period['trade_count']})",
f"{round_coin_value(period['abs_profit'], stats['stake_currency'])}",
f"{period['fiat_value']:.2f} {stats['fiat_display_currency']}",
f"{period['rel_profit']:.2%}",
] for period in stats['data']],
headers=[
f"{val.header} (count)",
f'{stake_cur}',
f'{fiat_disp_cur}',
'Profit %',
'Trades',
],
tablefmt='simple')
message = (
f'<b>{val.message} Profit over the last {timescale} {val.message2}</b>:\n'
f'<pre>{stats_tab}</pre>'
)
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
callback_path=val.callback, query=update.callback_query)
except RPCException as e:
self._send_msg(str(e))
@authorized_only @authorized_only
def _daily(self, update: Update, context: CallbackContext) -> None: def _daily(self, update: Update, context: CallbackContext) -> None:
""" """
@ -573,35 +637,7 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
stake_cur = self._config['stake_currency'] self._timeunit_stats(update, context, 'days')
fiat_disp_cur = self._config.get('fiat_display_currency', '')
try:
timescale = int(context.args[0]) if context.args else 7
except (TypeError, ValueError, IndexError):
timescale = 7
try:
stats = self._rpc._rpc_daily_profit(
timescale,
stake_cur,
fiat_disp_cur
)
stats_tab = tabulate(
[[day['date'],
f"{round_coin_value(day['abs_profit'], stats['stake_currency'])}",
f"{day['fiat_value']:.3f} {stats['fiat_display_currency']}",
f"{day['trade_count']} trades"] for day in stats['data']],
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}',
'Trades',
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
callback_path="update_daily", query=update.callback_query)
except RPCException as e:
self._send_msg(str(e))
@authorized_only @authorized_only
def _weekly(self, update: Update, context: CallbackContext) -> None: def _weekly(self, update: Update, context: CallbackContext) -> None:
@ -612,36 +648,7 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
stake_cur = self._config['stake_currency'] self._timeunit_stats(update, context, 'weeks')
fiat_disp_cur = self._config.get('fiat_display_currency', '')
try:
timescale = int(context.args[0]) if context.args else 8
except (TypeError, ValueError, IndexError):
timescale = 8
try:
stats = self._rpc._rpc_weekly_profit(
timescale,
stake_cur,
fiat_disp_cur
)
stats_tab = tabulate(
[[week['date'],
f"{round_coin_value(week['abs_profit'], stats['stake_currency'])}",
f"{week['fiat_value']:.3f} {stats['fiat_display_currency']}",
f"{week['trade_count']} trades"] for week in stats['data']],
headers=[
'Monday',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}',
'Trades',
],
tablefmt='simple')
message = f'<b>Weekly Profit over the last {timescale} weeks ' \
f'(starting from Monday)</b>:\n<pre>{stats_tab}</pre> '
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
callback_path="update_weekly", query=update.callback_query)
except RPCException as e:
self._send_msg(str(e))
@authorized_only @authorized_only
def _monthly(self, update: Update, context: CallbackContext) -> None: def _monthly(self, update: Update, context: CallbackContext) -> None:
@ -652,36 +659,7 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
stake_cur = self._config['stake_currency'] self._timeunit_stats(update, context, 'months')
fiat_disp_cur = self._config.get('fiat_display_currency', '')
try:
timescale = int(context.args[0]) if context.args else 6
except (TypeError, ValueError, IndexError):
timescale = 6
try:
stats = self._rpc._rpc_monthly_profit(
timescale,
stake_cur,
fiat_disp_cur
)
stats_tab = tabulate(
[[month['date'],
f"{round_coin_value(month['abs_profit'], stats['stake_currency'])}",
f"{month['fiat_value']:.3f} {stats['fiat_display_currency']}",
f"{month['trade_count']} trades"] for month in stats['data']],
headers=[
'Month',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}',
'Trades',
],
tablefmt='simple')
message = f'<b>Monthly Profit over the last {timescale} months' \
f'</b>:\n<pre>{stats_tab}</pre> '
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
callback_path="update_monthly", query=update.callback_query)
except RPCException as e:
self._send_msg(str(e))
@authorized_only @authorized_only
def _profit(self, update: Update, context: CallbackContext) -> None: def _profit(self, update: Update, context: CallbackContext) -> None:

View File

@ -289,6 +289,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (base) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
@ -316,6 +317,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in base currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',

View File

@ -161,6 +161,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (base) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
@ -188,6 +189,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in base currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',

View File

@ -7,7 +7,7 @@
coveralls==3.3.1 coveralls==3.3.1
flake8==4.0.1 flake8==4.0.1
flake8-tidy-imports==4.8.0 flake8-tidy-imports==4.8.0
mypy==0.960 mypy==0.961
pre-commit==2.19.0 pre-commit==2.19.0
pytest==7.1.2 pytest==7.1.2
pytest-asyncio==0.18.3 pytest-asyncio==0.18.3
@ -23,7 +23,7 @@ nbconvert==6.5.0
# mypy types # mypy types
types-cachetools==5.0.1 types-cachetools==5.0.1
types-filelock==3.2.6 types-filelock==3.2.7
types-requests==2.27.30 types-requests==2.27.30
types-tabulate==0.8.9 types-tabulate==0.8.9
types-python-dateutil==2.8.17 types-python-dateutil==2.8.17

View File

@ -1,4 +1,4 @@
# Include all requirements to run the bot. # Include all requirements to run the bot.
-r requirements.txt -r requirements.txt
plotly==5.8.0 plotly==5.8.2

View File

@ -2,7 +2,7 @@ numpy==1.22.4
pandas==1.4.2 pandas==1.4.2
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==1.85.57 ccxt==1.87.12
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==37.0.2 cryptography==37.0.2
aiohttp==3.8.1 aiohttp==3.8.1
@ -10,7 +10,7 @@ SQLAlchemy==1.4.37
python-telegram-bot==13.12 python-telegram-bot==13.12
arrow==1.2.2 arrow==1.2.2
cachetools==4.2.2 cachetools==4.2.2
requests==2.27.1 requests==2.28.0
urllib3==1.26.9 urllib3==1.26.9
jsonschema==4.6.0 jsonschema==4.6.0
TA-Lib==0.4.24 TA-Lib==0.4.24
@ -28,7 +28,7 @@ py_find_1st==1.1.5
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.6 python-rapidjson==1.6
# Properly format api responses # Properly format api responses
orjson==3.7.1 orjson==3.7.2
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2

View File

@ -87,6 +87,10 @@ function updateenv() {
echo "Failed installing Freqtrade" echo "Failed installing Freqtrade"
exit 1 exit 1
fi fi
echo "Installing freqUI"
freqtrade install-ui
echo "pip install completed" echo "pip install completed"
echo echo
if [[ $dev =~ ^[Yy]$ ]]; then if [[ $dev =~ ^[Yy]$ ]]; then

View File

@ -325,7 +325,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
Trade.query.session.flush() Trade.query.session.flush()
def create_mock_trades_usdt(fee, use_db: bool = True): def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True):
""" """
Create some fake trades ... Create some fake trades ...
""" """
@ -335,26 +335,29 @@ def create_mock_trades_usdt(fee, use_db: bool = True):
else: else:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
is_short1 = is_short if is_short is not None else True
is_short2 = is_short if is_short is not None else False
# Simulate dry_run entries # Simulate dry_run entries
trade = mock_trade_usdt_1(fee) trade = mock_trade_usdt_1(fee, is_short1)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_2(fee) trade = mock_trade_usdt_2(fee, is_short1)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_3(fee) trade = mock_trade_usdt_3(fee, is_short1)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_4(fee) trade = mock_trade_usdt_4(fee, is_short2)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_5(fee) trade = mock_trade_usdt_5(fee, is_short2)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_6(fee) trade = mock_trade_usdt_6(fee, is_short1)
add_trade(trade) add_trade(trade)
trade = mock_trade_usdt_7(fee) trade = mock_trade_usdt_7(fee, is_short1)
add_trade(trade) add_trade(trade)
if use_db: if use_db:
Trade.commit() Trade.commit()

View File

@ -6,47 +6,84 @@ from freqtrade.persistence.models import Order, Trade
MOCK_TRADE_COUNT = 6 MOCK_TRADE_COUNT = 6
def mock_order_usdt_1(): def entry_side(is_short: bool):
return "sell" if is_short else "buy"
def exit_side(is_short: bool):
return "buy" if is_short else "sell"
def direc(is_short: bool):
return "short" if is_short else "long"
def mock_order_usdt_1(is_short: bool):
return { return {
'id': '1234', 'id': f'prod_entry_1_{direc(is_short)}',
'symbol': 'ADA/USDT', 'symbol': 'LTC/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 2.0, 'price': 10.0,
'amount': 10.0, 'amount': 2.0,
'filled': 10.0, 'filled': 2.0,
'remaining': 0.0, 'remaining': 0.0,
} }
def mock_trade_usdt_1(fee): def mock_order_usdt_1_exit(is_short: bool):
return {
'id': f'prod_exit_1_{direc(is_short)}',
'symbol': 'LTC/USDT',
'status': 'closed',
'side': exit_side(is_short),
'type': 'limit',
'price': 8.0,
'amount': 2.0,
'filled': 2.0,
'remaining': 0.0,
}
def mock_trade_usdt_1(fee, is_short: bool):
"""
Simulate prod entry with open sell order
"""
trade = Trade( trade = Trade(
pair='ADA/USDT', pair='LTC/USDT',
stake_amount=20.0, stake_amount=20.0,
amount=10.0, amount=2.0,
amount_requested=10.0, amount_requested=2.0,
open_date=datetime.now(tz=timezone.utc) - timedelta(days=2, minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(days=2, minutes=5),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
is_open=True, is_open=False,
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17), open_rate=10.0,
open_rate=2.0, close_rate=8.0,
close_profit=-0.2,
close_profit_abs=-4.0,
exchange='binance', exchange='binance',
open_order_id='dry_run_buy_12345', strategy='SampleStrategy',
strategy='StrategyTestV2', open_order_id=f'prod_exit_1_{direc(is_short)}',
timeframe=5, timeframe=5,
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_1(), 'ADA/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_1(is_short), 'LTC/USDT', entry_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_1_exit(is_short),
'LTC/USDT', exit_side(is_short))
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_2(): def mock_order_usdt_2(is_short: bool):
return { return {
'id': '1235', 'id': f'1235_{direc(is_short)}',
'symbol': 'ETC/USDT', 'symbol': 'ETC/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 2.0, 'price': 2.0,
'amount': 100.0, 'amount': 100.0,
@ -55,12 +92,12 @@ def mock_order_usdt_2():
} }
def mock_order_usdt_2_sell(): def mock_order_usdt_2_exit(is_short: bool):
return { return {
'id': '12366', 'id': f'12366_{direc(is_short)}',
'symbol': 'ETC/USDT', 'symbol': 'ETC/USDT',
'status': 'closed', 'status': 'closed',
'side': 'sell', 'side': exit_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 2.05, 'price': 2.05,
'amount': 100.0, 'amount': 100.0,
@ -69,7 +106,7 @@ def mock_order_usdt_2_sell():
} }
def mock_trade_usdt_2(fee): def mock_trade_usdt_2(fee, is_short: bool):
""" """
Closed trade... Closed trade...
""" """
@ -82,30 +119,33 @@ def mock_trade_usdt_2(fee):
fee_close=fee.return_value, fee_close=fee.return_value,
open_rate=2.0, open_rate=2.0,
close_rate=2.05, close_rate=2.05,
close_profit=5.0, close_profit=0.05,
close_profit_abs=3.9875, close_profit_abs=3.9875,
exchange='binance', exchange='binance',
is_open=False, is_open=False,
open_order_id='dry_run_sell_12345', open_order_id=f'12366_{direc(is_short)}',
strategy='StrategyTestV2', strategy='StrategyTestV2',
timeframe=5, timeframe=5,
exit_reason='sell_signal', enter_tag='TEST1',
exit_reason='exit_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_2(), 'ETC/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_2(is_short), 'ETC/USDT', entry_side(is_short))
trade.orders.append(o) trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_2_sell(), 'ETC/USDT', 'sell') o = Order.parse_from_ccxt_object(
mock_order_usdt_2_exit(is_short), 'ETC/USDT', exit_side(is_short))
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_3(): def mock_order_usdt_3(is_short: bool):
return { return {
'id': '41231a12a', 'id': f'41231a12a_{direc(is_short)}',
'symbol': 'XRP/USDT', 'symbol': 'XRP/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 1.0, 'price': 1.0,
'amount': 30.0, 'amount': 30.0,
@ -114,12 +154,12 @@ def mock_order_usdt_3():
} }
def mock_order_usdt_3_sell(): def mock_order_usdt_3_exit(is_short: bool):
return { return {
'id': '41231a666a', 'id': f'41231a666a_{direc(is_short)}',
'symbol': 'XRP/USDT', 'symbol': 'XRP/USDT',
'status': 'closed', 'status': 'closed',
'side': 'sell', 'side': exit_side(is_short),
'type': 'stop_loss_limit', 'type': 'stop_loss_limit',
'price': 1.1, 'price': 1.1,
'average': 1.1, 'average': 1.1,
@ -129,7 +169,7 @@ def mock_order_usdt_3_sell():
} }
def mock_trade_usdt_3(fee): def mock_trade_usdt_3(fee, is_short: bool):
""" """
Closed trade Closed trade
""" """
@ -142,29 +182,32 @@ def mock_trade_usdt_3(fee):
fee_close=fee.return_value, fee_close=fee.return_value,
open_rate=1.0, open_rate=1.0,
close_rate=1.1, close_rate=1.1,
close_profit=10.0, close_profit=0.1,
close_profit_abs=9.8425, close_profit_abs=9.8425,
exchange='binance', exchange='binance',
is_open=False, is_open=False,
strategy='StrategyTestV2', strategy='StrategyTestV2',
timeframe=5, timeframe=5,
enter_tag='TEST3',
exit_reason='roi', exit_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc), close_date=datetime.now(tz=timezone.utc),
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_3(), 'XRP/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_3(is_short), 'XRP/USDT', entry_side(is_short))
trade.orders.append(o) trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_3_sell(), 'XRP/USDT', 'sell') o = Order.parse_from_ccxt_object(mock_order_usdt_3_exit(is_short),
'XRP/USDT', exit_side(is_short))
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_4(): def mock_order_usdt_4(is_short: bool):
return { return {
'id': 'prod_buy_12345', 'id': f'prod_buy_12345_{direc(is_short)}',
'symbol': 'ETC/USDT', 'symbol': 'ETC/USDT',
'status': 'open', 'status': 'open',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 2.0, 'price': 2.0,
'amount': 10.0, 'amount': 10.0,
@ -173,7 +216,7 @@ def mock_order_usdt_4():
} }
def mock_trade_usdt_4(fee): def mock_trade_usdt_4(fee, is_short: bool):
""" """
Simulate prod entry Simulate prod entry
""" """
@ -188,21 +231,22 @@ def mock_trade_usdt_4(fee):
is_open=True, is_open=True,
open_rate=2.0, open_rate=2.0,
exchange='binance', exchange='binance',
open_order_id='prod_buy_12345', open_order_id=f'prod_buy_12345_{direc(is_short)}',
strategy='StrategyTestV2', strategy='StrategyTestV2',
timeframe=5, timeframe=5,
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_4(), 'ETC/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_4(is_short), 'ETC/USDT', entry_side(is_short))
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_5(): def mock_order_usdt_5(is_short: bool):
return { return {
'id': 'prod_buy_3455', 'id': f'prod_buy_3455_{direc(is_short)}',
'symbol': 'XRP/USDT', 'symbol': 'XRP/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 2.0, 'price': 2.0,
'amount': 10.0, 'amount': 10.0,
@ -211,12 +255,12 @@ def mock_order_usdt_5():
} }
def mock_order_usdt_5_stoploss(): def mock_order_usdt_5_stoploss(is_short: bool):
return { return {
'id': 'prod_stoploss_3455', 'id': f'prod_stoploss_3455_{direc(is_short)}',
'symbol': 'XRP/USDT', 'symbol': 'XRP/USDT',
'status': 'open', 'status': 'open',
'side': 'sell', 'side': exit_side(is_short),
'type': 'stop_loss_limit', 'type': 'stop_loss_limit',
'price': 2.0, 'price': 2.0,
'amount': 10.0, 'amount': 10.0,
@ -225,7 +269,7 @@ def mock_order_usdt_5_stoploss():
} }
def mock_trade_usdt_5(fee): def mock_trade_usdt_5(fee, is_short: bool):
""" """
Simulate prod entry with stoploss Simulate prod entry with stoploss
""" """
@ -241,22 +285,23 @@ def mock_trade_usdt_5(fee):
open_rate=2.0, open_rate=2.0,
exchange='binance', exchange='binance',
strategy='SampleStrategy', strategy='SampleStrategy',
stoploss_order_id='prod_stoploss_3455', stoploss_order_id=f'prod_stoploss_3455_{direc(is_short)}',
timeframe=5, timeframe=5,
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_5(), 'XRP/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_5(is_short), 'XRP/USDT', entry_side(is_short))
trade.orders.append(o) trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_5_stoploss(), 'XRP/USDT', 'stoploss') o = Order.parse_from_ccxt_object(mock_order_usdt_5_stoploss(is_short), 'XRP/USDT', 'stoploss')
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_6(): def mock_order_usdt_6(is_short: bool):
return { return {
'id': 'prod_buy_6', 'id': f'prod_entry_6_{direc(is_short)}',
'symbol': 'LTC/USDT', 'symbol': 'LTC/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 10.0, 'price': 10.0,
'amount': 2.0, 'amount': 2.0,
@ -265,12 +310,12 @@ def mock_order_usdt_6():
} }
def mock_order_usdt_6_sell(): def mock_order_usdt_6_exit(is_short: bool):
return { return {
'id': 'prod_sell_6', 'id': f'prod_exit_6_{direc(is_short)}',
'symbol': 'LTC/USDT', 'symbol': 'LTC/USDT',
'status': 'open', 'status': 'open',
'side': 'sell', 'side': exit_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 12.0, 'price': 12.0,
'amount': 2.0, 'amount': 2.0,
@ -279,7 +324,7 @@ def mock_order_usdt_6_sell():
} }
def mock_trade_usdt_6(fee): def mock_trade_usdt_6(fee, is_short: bool):
""" """
Simulate prod entry with open sell order Simulate prod entry with open sell order
""" """
@ -295,69 +340,49 @@ def mock_trade_usdt_6(fee):
open_rate=10.0, open_rate=10.0,
exchange='binance', exchange='binance',
strategy='SampleStrategy', strategy='SampleStrategy',
open_order_id="prod_sell_6", open_order_id=f'prod_exit_6_{direc(is_short)}',
timeframe=5, timeframe=5,
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_6(), 'LTC/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_6(is_short), 'LTC/USDT', entry_side(is_short))
trade.orders.append(o) trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_6_sell(), 'LTC/USDT', 'sell') o = Order.parse_from_ccxt_object(mock_order_usdt_6_exit(is_short),
'LTC/USDT', exit_side(is_short))
trade.orders.append(o) trade.orders.append(o)
return trade return trade
def mock_order_usdt_7(): def mock_order_usdt_7(is_short: bool):
return { return {
'id': 'prod_buy_7', 'id': f'1234_{direc(is_short)}',
'symbol': 'LTC/USDT', 'symbol': 'ADA/USDT',
'status': 'closed', 'status': 'closed',
'side': 'buy', 'side': entry_side(is_short),
'type': 'limit', 'type': 'limit',
'price': 10.0, 'price': 2.0,
'amount': 2.0, 'amount': 10.0,
'filled': 2.0, 'filled': 10.0,
'remaining': 0.0, 'remaining': 0.0,
} }
def mock_order_usdt_7_sell(): def mock_trade_usdt_7(fee, is_short: bool):
return {
'id': 'prod_sell_7',
'symbol': 'LTC/USDT',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 8.0,
'amount': 2.0,
'filled': 2.0,
'remaining': 0.0,
}
def mock_trade_usdt_7(fee):
"""
Simulate prod entry with open sell order
"""
trade = Trade( trade = Trade(
pair='LTC/USDT', pair='ADA/USDT',
stake_amount=20.0, stake_amount=20.0,
amount=2.0, amount=10.0,
amount_requested=2.0, amount_requested=10.0,
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
is_open=False, is_open=True,
open_rate=10.0, open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
close_rate=8.0, open_rate=2.0,
close_profit=-0.2,
close_profit_abs=-4.0,
exchange='binance', exchange='binance',
strategy='SampleStrategy', open_order_id=f'1234_{direc(is_short)}',
open_order_id="prod_sell_6", strategy='StrategyTestV2',
timeframe=5, timeframe=5,
is_short=is_short,
) )
o = Order.parse_from_ccxt_object(mock_order_usdt_7(), 'LTC/USDT', 'buy') o = Order.parse_from_ccxt_object(mock_order_usdt_7(is_short), 'ADA/USDT', entry_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_usdt_7_sell(), 'LTC/USDT', 'sell')
trade.orders.append(o) trade.orders.append(o)
return trade return trade

View File

@ -762,8 +762,8 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
create_mock_trades_usdt(fee) create_mock_trades_usdt(fee)
pm.refresh_pairlist() pm.refresh_pairlist()
assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', 'LTC/USDT',
'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'LTC/USDT'] 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', ]
# assert log_has_re(r'Removing pair .* since .* is below .*', caplog) # assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
# Move to "outside" of lookback window, so original sorting is restored. # Move to "outside" of lookback window, so original sorting is restored.

View File

@ -11,11 +11,11 @@ from freqtrade.edge import PairInfo
from freqtrade.enums import SignalDirection, State, TradingMode from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.persistence.models import Order
from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.persistence.pairlock_middleware import PairLocks
from freqtrade.rpc import RPC, RPCException from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot,
patch_get_signal)
# Functions for recurrent object patching # Functions for recurrent object patching
@ -284,7 +284,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert isnan(fiat_profit_sum) assert isnan(fiat_profit_sum)
def test_rpc_daily_profit(default_conf, update, ticker, fee, def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None: limit_buy_order, limit_sell_order, markets, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -294,45 +294,35 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
markets=PropertyMock(return_value=markets) markets=PropertyMock(return_value=markets)
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) create_mock_trades_usdt(fee)
stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] stake_currency = default_conf_usdt['stake_currency']
fiat_display_currency = default_conf_usdt['fiat_display_currency']
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate buy & sell
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data # Try valid data
update.message.text = '/daily 2' days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency)
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
assert len(days['data']) == 7 assert len(days['data']) == 7
assert days['stake_currency'] == default_conf['stake_currency'] assert days['stake_currency'] == default_conf_usdt['stake_currency']
assert days['fiat_display_currency'] == default_conf['fiat_display_currency'] assert days['fiat_display_currency'] == default_conf_usdt['fiat_display_currency']
for day in days['data']: for day in days['data']:
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD'] # {'date': datetime.date(2022, 6, 11), 'abs_profit': 13.8299999,
assert (day['abs_profit'] == 0.0 or # 'starting_balance': 1055.37, 'rel_profit': 0.0131044,
day['abs_profit'] == 0.00006217) # 'fiat_value': 0.0, 'trade_count': 2}
assert day['abs_profit'] in (0.0, pytest.approx(13.8299999), pytest.approx(-4.0))
assert (day['fiat_value'] == 0.0 or assert day['rel_profit'] in (0.0, pytest.approx(0.01310441), pytest.approx(-0.00377583))
day['fiat_value'] == 0.76748865) assert day['trade_count'] in (0, 1, 2)
assert day['starting_balance'] in (pytest.approx(1059.37), pytest.approx(1055.37))
assert day['fiat_value'] in (0.0, )
# ensure first day is current date # ensure first day is current date
assert str(days['data'][0]['date']) == str(datetime.utcnow().date()) assert str(days['data'][0]['date']) == str(datetime.utcnow().date())
# Try invalid data # Try invalid data
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'): with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency) rpc._rpc_timeunit_profit(0, stake_currency, fiat_display_currency)
@pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
@ -416,13 +406,8 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
assert stoploss_mock.call_count == 0 assert stoploss_mock.call_count == 0
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1)
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -430,10 +415,9 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
get_fee=fee, get_fee=fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) stake_currency = default_conf_usdt['stake_currency']
stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf_usdt['fiat_display_currency']
fiat_display_currency = default_conf['fiat_display_currency']
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
@ -446,75 +430,40 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert res['latest_trade_timestamp'] == 0 assert res['latest_trade_timestamp'] == 0
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'sell')
trade.update_trade(oobj)
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_sell_up
)
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
freqtradebot.enter_positions()
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_sell_up
)
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) assert pytest.approx(stats['profit_closed_coin']) == 9.83
assert prec_satoshi(stats['profit_closed_percent_mean'], 6.2) assert pytest.approx(stats['profit_closed_percent_mean']) == -1.67
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) assert pytest.approx(stats['profit_closed_fiat']) == 10.813
assert prec_satoshi(stats['profit_all_coin'], 5.802e-05) assert pytest.approx(stats['profit_all_coin']) == -77.45964918
assert prec_satoshi(stats['profit_all_percent_mean'], 2.89) assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
assert prec_satoshi(stats['profit_all_fiat'], 0.8703) assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
assert stats['trade_count'] == 2 assert stats['trade_count'] == 7
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == '2 days ago'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == '17 minutes ago'
assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02') assert stats['avg_duration'] in ('0:17:40')
assert stats['best_pair'] == 'ETH/BTC' assert stats['best_pair'] == 'XRP/USDT'
assert prec_satoshi(stats['best_rate'], 6.2) assert stats['best_rate'] == 10.0
# Test non-available pair # Test non-available pair
mocker.patch('freqtrade.exchange.Exchange.get_rate', mocker.patch('freqtrade.exchange.Exchange.get_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available")))
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert stats['trade_count'] == 2 assert stats['trade_count'] == 7
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == '2 days ago'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == '17 minutes ago'
assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02') assert stats['avg_duration'] in ('0:17:40')
assert stats['best_pair'] == 'ETH/BTC' assert stats['best_pair'] == 'XRP/USDT'
assert prec_satoshi(stats['best_rate'], 6.2) assert stats['best_rate'] == 10.0
assert isnan(stats['profit_all_coin']) assert isnan(stats['profit_all_coin'])
# Test that rpc_trade_statistics can handle trades that lacks # Test that rpc_trade_statistics can handle trades that lacks
# trade.open_rate (it is set to None) # trade.open_rate (it is set to None)
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, def test_rpc_trade_statistics_closed(mocker, default_conf_usdt, ticker, fee):
ticker_sell_up, limit_buy_order, limit_sell_order):
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0) return_value=1.1)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -522,46 +471,32 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
get_fee=fee, get_fee=fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
stake_currency = default_conf['stake_currency'] stake_currency = default_conf_usdt['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf_usdt['fiat_display_currency']
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_sell_up,
get_fee=fee
)
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
for trade in Trade.query.order_by(Trade.id).all(): for trade in Trade.query.order_by(Trade.id).all():
trade.open_rate = None trade.open_rate = None
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert prec_satoshi(stats['profit_closed_coin'], 0) assert stats['profit_closed_coin'] == 0
assert prec_satoshi(stats['profit_closed_percent_mean'], 0) assert stats['profit_closed_percent_mean'] == 0
assert prec_satoshi(stats['profit_closed_fiat'], 0) assert stats['profit_closed_fiat'] == 0
assert prec_satoshi(stats['profit_all_coin'], 0) assert stats['profit_all_coin'] == 0
assert prec_satoshi(stats['profit_all_percent_mean'], 0) assert stats['profit_all_percent_mean'] == 0
assert prec_satoshi(stats['profit_all_fiat'], 0) assert stats['profit_all_fiat'] == 0
assert stats['trade_count'] == 1 assert stats['trade_count'] == 7
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == '2 days ago'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == '17 minutes ago'
assert stats['avg_duration'] == '0:00:00' assert stats['avg_duration'] == '0:00:00'
assert stats['best_pair'] == 'ETH/BTC' assert stats['best_pair'] == 'XRP/USDT'
assert prec_satoshi(stats['best_rate'], 6.2) assert stats['best_rate'] == 10.0
def test_rpc_balance_handle_error(default_conf, mocker): def test_rpc_balance_handle_error(default_conf, mocker):
@ -913,8 +848,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
assert cancel_order_mock.call_count == 3 assert cancel_order_mock.call_count == 3
def test_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -923,34 +857,21 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
get_fee=fee, get_fee=fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_performance() res = rpc._rpc_performance()
assert len(res) == 1 assert len(res) == 3
assert res[0]['pair'] == 'ETH/BTC' assert res[0]['pair'] == 'XRP/USDT'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert res[0]['profit_pct'] == 10.0
def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -964,34 +885,22 @@ def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
create_mock_trades_usdt(fee)
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_enter_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1 assert len(res) == 3
assert res[0]['enter_tag'] == 'Other' assert res[0]['enter_tag'] == 'TEST3'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert res[0]['profit_pct'] == 10.0
trade.enter_tag = "TEST_TAG"
res = rpc._rpc_enter_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1 assert len(res) == 3
assert res[0]['enter_tag'] == 'TEST_TAG' assert res[0]['enter_tag'] == 'TEST3'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert res[0]['profit_pct'] == 10.0
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
@ -1023,8 +932,7 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
def test_exit_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -1033,39 +941,22 @@ def test_exit_reason_performance_handle(default_conf, ticker, limit_buy_order, f
get_fee=fee, get_fee=fee,
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_exit_reason_performance(None) res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 1 assert len(res) == 3
assert res[0]['exit_reason'] == 'Other' assert res[0]['exit_reason'] == 'roi'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert res[0]['profit_pct'] == 10.0
trade.exit_reason = "TEST1" assert res[1]['exit_reason'] == 'exit_signal'
res = rpc._rpc_exit_reason_performance(None) assert res[2]['exit_reason'] == 'Other'
assert len(res) == 1
assert res[0]['exit_reason'] == 'TEST1'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
@ -1097,8 +988,7 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -1112,35 +1002,14 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_mix_tag_performance(None) res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 1 assert len(res) == 3
assert res[0]['mix_tag'] == 'Other Other' assert res[0]['mix_tag'] == 'TEST3 roi'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert res[0]['profit_pct'] == 10.0
trade.enter_tag = "TESTBUY"
trade.exit_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 1
assert res[0]['mix_tag'] == 'TESTBUY TESTSELL'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):

View File

@ -27,8 +27,9 @@ from freqtrade.persistence.models import Order
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.rpc import RPCException
from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.rpc.telegram import Telegram, authorized_only
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot, from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, create_mock_trades_usdt,
log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist) get_patched_freqtradebot, log_has, log_has_re, patch_exchange,
patch_get_signal, patch_whitelist)
class DummyCls(Telegram): class DummyCls(Telegram):
@ -404,12 +405,10 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
limit_sell_order, mocker) -> None:
default_conf['max_open_trades'] = 1
mocker.patch( mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0 return_value=1.1
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -417,25 +416,12 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
# Move date to within day
time_machine.move_to('2022-06-11 08:00:00+00:00')
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data # Try valid data
# /daily 2 # /daily 2
@ -446,10 +432,11 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
assert "Daily Profit over the last 2 days</b>:" in msg_mock.call_args_list[0][0][0] assert "Daily Profit over the last 2 days</b>:" in msg_mock.call_args_list[0][0][0]
assert 'Day ' in msg_mock.call_args_list[0][0][0] assert 'Day ' in msg_mock.call_args_list[0][0][0]
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(2) 13.83 USDT 15.21 USD 1.31%' in msg_mock.call_args_list[0][0][0]
assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
@ -458,32 +445,23 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "Daily Profit over the last 7 days</b>:" in msg_mock.call_args_list[0][0][0] assert "Daily Profit over the last 7 days</b>:" in msg_mock.call_args_list[0][0][0]
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert str((datetime.utcnow() - timedelta(days=5)).date()) in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0]
assert '(1)' in msg_mock.call_args_list[0][0][0]
assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades
n = freqtradebot.enter_positions()
assert n == 2
trades = Trade.query.all()
for trade in trades:
trade.update_trade(oobj)
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# /daily 1 # /daily 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._daily(update=update, context=context) telegram._daily(update=update, context=context)
assert str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0] assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0] assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0]
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
@ -512,15 +490,14 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
context = MagicMock() context = MagicMock()
context.args = ["today"] context.args = ["today"]
telegram._daily(update=update, context=context) telegram._daily(update=update, context=context)
assert str('Daily Profit over the last 7 days</b>:') in msg_mock.call_args_list[0][0][0] assert 'Daily Profit over the last 7 days</b>:' in msg_mock.call_args_list[0][0][0]
def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee, def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
limit_sell_order, mocker) -> None: default_conf_usdt['max_open_trades'] = 1
default_conf['max_open_trades'] = 1
mocker.patch( mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0 return_value=1.1
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -528,25 +505,10 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
# Move to saturday - so all trades are within that week
patch_get_signal(freqtradebot) time_machine.move_to('2022-06-11')
create_mock_trades_usdt(fee)
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data # Try valid data
# /weekly 2 # /weekly 2
@ -560,10 +522,10 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
today = datetime.utcnow().date() today = datetime.utcnow().date()
first_iso_day_of_current_week = today - timedelta(days=today.weekday()) first_iso_day_of_current_week = today - timedelta(days=today.weekday())
assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0] assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert '(3)' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
@ -573,44 +535,10 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
assert "Weekly Profit over the last 8 weeks (starting from Monday)</b>:" \ assert "Weekly Profit over the last 8 weeks (starting from Monday)</b>:" \
in msg_mock.call_args_list[0][0][0] in msg_mock.call_args_list[0][0][0]
assert 'Weekly' in msg_mock.call_args_list[0][0][0] assert 'Weekly' in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert '(3)' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades
n = freqtradebot.enter_positions()
assert n == 2
trades = Trade.query.all()
for trade in trades:
trade.update_trade(oobj)
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# /weekly 1
# By default, the 8 previous weeks are shown
# So the previous modified trade should be excluded from the stats
context = MagicMock()
context.args = ["1"]
telegram._weekly(update=update, context=context)
assert str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0]
def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Try invalid data # Try invalid data
msg_mock.reset_mock() msg_mock.reset_mock()
@ -629,16 +557,17 @@ def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
context = MagicMock() context = MagicMock()
context.args = ["this week"] context.args = ["this week"]
telegram._weekly(update=update, context=context) telegram._weekly(update=update, context=context)
assert str('Weekly Profit over the last 8 weeks (starting from Monday)</b>:') \ assert (
'Weekly Profit over the last 8 weeks (starting from Monday)</b>:'
in msg_mock.call_args_list[0][0][0] in msg_mock.call_args_list[0][0][0]
)
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee, def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
limit_sell_order, mocker) -> None: default_conf_usdt['max_open_trades'] = 1
default_conf['max_open_trades'] = 1
mocker.patch( mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0 return_value=1.1
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -646,25 +575,10 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
# Move to day within the month so all mock trades fall into this week.
patch_get_signal(freqtradebot) time_machine.move_to('2022-06-11')
create_mock_trades_usdt(fee)
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data # Try valid data
# /monthly 2 # /monthly 2
@ -677,10 +591,10 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
today = datetime.utcnow().date() today = datetime.utcnow().date()
current_month = f"{today.year}-{today.month:02} " current_month = f"{today.year}-{today.month:02} "
assert current_month in msg_mock.call_args_list[0][0][0] assert current_month in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert '(3)' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
@ -691,24 +605,13 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
assert 'Monthly Profit over the last 6 months</b>:' in msg_mock.call_args_list[0][0][0] assert 'Monthly Profit over the last 6 months</b>:' in msg_mock.call_args_list[0][0][0]
assert 'Month ' in msg_mock.call_args_list[0][0][0] assert 'Month ' in msg_mock.call_args_list[0][0][0]
assert current_month in msg_mock.call_args_list[0][0][0] assert current_month in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert '(3)' in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] assert '(0)' in msg_mock.call_args_list[0][0][0]
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades
n = freqtradebot.enter_positions()
assert n == 2
trades = Trade.query.all()
for trade in trades:
trade.update_trade(oobj)
trade.update_trade(oobjs)
trade.close_date = datetime.utcnow()
trade.is_open = False
# /monthly 12 # /monthly 12
context = MagicMock() context = MagicMock()
@ -716,24 +619,14 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
telegram._monthly(update=update, context=context) telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Monthly Profit over the last 12 months</b>:' in msg_mock.call_args_list[0][0][0] assert 'Monthly Profit over the last 12 months</b>:' in msg_mock.call_args_list[0][0][0]
assert str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0] assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0] assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0] assert '(3)' in msg_mock.call_args_list[0][0][0]
# The one-digit months should contain a zero, Eg: September 2021 = "2021-09" # The one-digit months should contain a zero, Eg: September 2021 = "2021-09"
# Since we loaded the last 12 months, any month should appear # Since we loaded the last 12 months, any month should appear
assert str('-09') in msg_mock.call_args_list[0][0][0] assert str('-09') in msg_mock.call_args_list[0][0][0]
def test_monthly_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Try invalid data # Try invalid data
msg_mock.reset_mock() msg_mock.reset_mock()
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -754,16 +647,16 @@ def test_monthly_wrong_input(default_conf, update, ticker, mocker) -> None:
assert str('Monthly Profit over the last 6 months</b>:') in msg_mock.call_args_list[0][0][0] assert str('Monthly Profit over the last 6 months</b>:') in msg_mock.call_args_list[0][0][0]
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, mocker) -> None: limit_sell_order_usdt, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
telegram._profit(update=update, context=MagicMock()) telegram._profit(update=update, context=MagicMock())
@ -775,10 +668,6 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
context = MagicMock() context = MagicMock()
# Test with invalid 2nd argument (should silently pass) # Test with invalid 2nd argument (should silently pass)
context.args = ["aaa"] context.args = ["aaa"]
@ -786,15 +675,16 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01) mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=1000)
assert ('∙ `-0.000005 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`' assert ('∙ `0.298 USDT (0.50%) (0.03 \N{GREEK CAPITAL LETTER SIGMA}%)`'
in msg_mock.call_args_list[-1][0][0]) in msg_mock.call_args_list[-1][0][0])
msg_mock.reset_mock() msg_mock.reset_mock()
# Update the ticker with a market going up # Update the ticker with a market going up
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up)
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') oobj = Order.parse_from_ccxt_object(
limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
trade.update_trade(oobj) trade.update_trade(oobj)
trade.close_date = datetime.now(timezone.utc) trade.close_date = datetime.now(timezone.utc)
@ -805,15 +695,15 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
telegram._profit(update=update, context=context) telegram._profit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0]
assert ('∙ `0.00006217 BTC (6.20%) (0.62 \N{GREEK CAPITAL LETTER SIGMA}%)`' assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`'
in msg_mock.call_args_list[-1][0][0]) in msg_mock.call_args_list[-1][0][0])
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '∙ `6.253 USD`' in msg_mock.call_args_list[-1][0][0]
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
assert ('∙ `0.00006217 BTC (6.20%) (0.62 \N{GREEK CAPITAL LETTER SIGMA}%)`' assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`'
in msg_mock.call_args_list[-1][0][0]) in msg_mock.call_args_list[-1][0][0])
assert '∙ `0.933 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/BTC: 6.20%`' 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]
@pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
@ -1350,71 +1240,43 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None:
assert fbuy_mock.call_count == 1 assert fbuy_mock.call_count == 1
def test_telegram_performance_handle(default_conf, update, ticker, fee, def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None:
limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
telegram._performance(update=update, context=MagicMock()) telegram._performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>XRP/USDT\t9.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
def test_telegram_entry_tag_performance_handle( def test_telegram_entry_tag_performance_handle(
default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, mocker) -> None: default_conf_usdt, update, ticker, fee, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
# Create some test data create_mock_trades_usdt(fee)
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
trade.enter_tag = "TESTBUY"
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock() context = MagicMock()
telegram._enter_tag_performance(update=update, context=context) 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 '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>TEST1\t3.987 USDT (5.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair] context.args = ['XRP/USDT']
telegram._enter_tag_performance(update=update, context=context) telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
@ -1427,37 +1289,24 @@ def test_telegram_entry_tag_performance_handle(
assert "Error" in msg_mock.call_args_list[0][0][0] assert "Error" in msg_mock.call_args_list[0][0][0]
def test_telegram_exit_reason_performance_handle(default_conf, update, ticker, fee, def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None: mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
# Create some test data create_mock_trades_usdt(fee)
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
trade.exit_reason = 'TESTSELL'
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock() context = MagicMock()
telegram._exit_reason_performance(update=update, context=context) 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 '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>roi\t9.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair] context.args = ['XRP/USDT']
telegram._exit_reason_performance(update=update, context=context) telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
@ -1471,43 +1320,27 @@ def test_telegram_exit_reason_performance_handle(default_conf, update, ticker, f
assert "Error" in msg_mock.call_args_list[0][0][0] assert "Error" in msg_mock.call_args_list[0][0][0]
def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee, def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None: mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
) )
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() create_mock_trades_usdt(fee)
trade = Trade.query.first()
assert trade
trade.enter_tag = "TESTBUY"
trade.exit_reason = "TESTSELL"
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock() context = MagicMock()
telegram._mix_tag_performance(update=update, context=context) telegram._mix_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0]
assert ('<code>TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' assert ('<code>TEST3 roi\t9.842 USDT (10.00%) (1)</code>'
in msg_mock.call_args_list[0][0][0]) in msg_mock.call_args_list[0][0][0])
context.args = [trade.pair] context.args = ['XRP/USDT']
telegram._mix_tag_performance(update=update, context=context) telegram._mix_tag_performance(update=update, context=context)
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2

View File

@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103, protected-access # pragma pylint: disable=missing-docstring, C0103, protected-access
from datetime import datetime, timedelta
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
@ -7,6 +8,7 @@ from requests import RequestException
from freqtrade.enums import ExitType, RPCMessageType from freqtrade.enums import ExitType, RPCMessageType
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.discord import Discord
from freqtrade.rpc.webhook import Webhook from freqtrade.rpc.webhook import Webhook
from tests.conftest import get_patched_freqtradebot, log_has from tests.conftest import get_patched_freqtradebot, log_has
@ -406,3 +408,42 @@ def test__send_msg_with_raw_format(default_conf, mocker, caplog):
webhook._send_msg(msg) webhook._send_msg(msg)
assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}} assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}}
def test_send_msg_discord(default_conf, mocker):
default_conf["discord"] = {
'enabled': True,
'webhook_url': "https://webhookurl..."
}
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
discord = Discord(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = {
'type': RPCMessageType.EXIT_FILL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'direction': 'Long',
'gain': "profit",
'close_rate': 0.005,
'amount': 0.8,
'order_type': 'limit',
'open_date': datetime.now() - timedelta(days=1),
'close_date': datetime.now(),
'open_rate': 0.004,
'current_rate': 0.005,
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'enter_tag': 'enter_tagggg',
'exit_reason': ExitType.STOP_LOSS.value,
}
discord.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert 'embeds' in msg_mock.call_args_list[0][0][0]
assert 'title' in msg_mock.call_args_list[0][0][0]['embeds'][0]
assert 'color' in msg_mock.call_args_list[0][0][0]['embeds'][0]
assert 'fields' in msg_mock.call_args_list[0][0][0]['embeds'][0]

View File

@ -20,7 +20,8 @@ from freqtrade.strategy.hyper import detect_parameters
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter, from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter) DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re from tests.conftest import (CURRENT_TEST_STRATEGY, TRADE_SIDES, create_mock_trades, log_has,
log_has_re)
from .strats.strategy_test_v3 import StrategyTestV3 from .strats.strategy_test_v3 import StrategyTestV3
@ -812,6 +813,28 @@ def test_strategy_safe_wrapper(value):
assert ret == value assert ret == value
@pytest.mark.usefixtures("init_persistence")
def test_strategy_safe_wrapper_trade_copy(fee):
create_mock_trades(fee)
def working_method(trade):
assert len(trade.orders) > 0
assert trade.orders
trade.orders = []
assert len(trade.orders) == 0
return trade
trade = Trade.get_open_trades()[0]
# Don't assert anything before strategy_wrapper.
# This ensures that relationship loading works correctly.
ret = strategy_safe_wrapper(working_method, message='DeadBeef')(trade=trade)
assert isinstance(ret, Trade)
assert id(trade) != id(ret)
# Did not modify the original order
assert len(trade.orders) > 0
assert len(ret.orders) == 0
def test_hyperopt_parameters(): def test_hyperopt_parameters():
from skopt.space import Categorical, Integer, Real from skopt.space import Categorical, Integer, Real
with pytest.raises(OperationalException, match=r"Name is determined.*"): with pytest.raises(OperationalException, match=r"Name is determined.*"):

View File

@ -210,13 +210,14 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
# #
# mocking the ticker: price is falling ... # mocking the ticker: price is falling ...
enter_price = limit_order['buy']['price'] enter_price = limit_order['buy']['price']
ticker_val = {
'bid': enter_price,
'ask': enter_price,
'last': enter_price,
}
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={ fetch_ticker=MagicMock(return_value=ticker_val),
'bid': enter_price * buy_price_mult,
'ask': enter_price * buy_price_mult,
'last': enter_price * buy_price_mult,
}),
get_fee=fee, get_fee=fee,
) )
############################################# #############################################
@ -229,9 +230,12 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.query.first()
caplog.clear() caplog.clear()
oobj = Order.parse_from_ccxt_object(limit_order['buy'], 'ADA/USDT', 'buy')
trade.update_trade(oobj)
############################################# #############################################
ticker_val.update({
'bid': enter_price * buy_price_mult,
'ask': enter_price * buy_price_mult,
'last': enter_price * buy_price_mult,
})
# stoploss shoud be hit # stoploss shoud be hit
assert freqtrade.handle_trade(trade) is not ignore_strat_sl assert freqtrade.handle_trade(trade) is not ignore_strat_sl
@ -3771,6 +3775,7 @@ def test_exit_profit_only(
trade = Trade.query.first() trade = Trade.query.first()
assert trade.is_short == is_short assert trade.is_short == is_short
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
trade.update_order(limit_order[eside])
trade.update_trade(oobj) trade.update_trade(oobj)
freqtrade.wallets.update() freqtrade.wallets.update()
if profit_only: if profit_only:
@ -4059,6 +4064,7 @@ def test_trailing_stop_loss_positive(
trade = Trade.query.first() trade = Trade.query.first()
assert trade.is_short == is_short assert trade.is_short == is_short
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
trade.update_order(limit_order[eside])
trade.update_trade(oobj) trade.update_trade(oobj)
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
# stop-loss not reached # stop-loss not reached