Merge branch 'develop' into main_refactoring
This commit is contained in:
commit
2959156070
@ -68,9 +68,9 @@ For any other type of installation please refer to [Installation doc](https://ww
|
|||||||
### Bot commands
|
### Bot commands
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
|
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
|
||||||
[--strategy-path PATH] [--dynamic-whitelist [INT]]
|
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
|
||||||
[--db-url PATH]
|
[--db-url PATH] [--sd-notify]
|
||||||
{backtesting,edge,hyperopt} ...
|
{backtesting,edge,hyperopt} ...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
@ -84,6 +84,7 @@ positional arguments:
|
|||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: None). Multiple
|
Specify configuration file (default: None). Multiple
|
||||||
@ -100,6 +101,7 @@ optional arguments:
|
|||||||
--db-url PATH Override trades database URL, this is useful if
|
--db-url PATH Override trades database URL, this is useful if
|
||||||
dry_run is enabled or in custom deployments (default:
|
dry_run is enabled or in custom deployments (default:
|
||||||
None).
|
None).
|
||||||
|
--sd-notify Notify systemd service manager.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Telegram RPC commands
|
### Telegram RPC commands
|
||||||
|
@ -39,12 +39,12 @@
|
|||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
"sell": "limit",
|
"sell": "limit",
|
||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": "false",
|
"stoploss_on_exchange": false,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
},
|
},
|
||||||
"order_time_in_force": {
|
"order_time_in_force": {
|
||||||
"buy": "gtc",
|
"buy": "gtc",
|
||||||
"sell": "gtc",
|
"sell": "gtc"
|
||||||
},
|
},
|
||||||
"pairlist": {
|
"pairlist": {
|
||||||
"method": "VolumePairList",
|
"method": "VolumePairList",
|
||||||
|
@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it.
|
|||||||
## Bot commands
|
## Bot commands
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
|
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
|
||||||
[--strategy-path PATH] [--dynamic-whitelist [INT]]
|
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
|
||||||
[--db-url PATH]
|
[--db-url PATH] [--sd-notify]
|
||||||
{backtesting,edge,hyperopt} ...
|
{backtesting,edge,hyperopt} ...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
@ -22,6 +22,7 @@ positional arguments:
|
|||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: None). Multiple
|
Specify configuration file (default: None). Multiple
|
||||||
@ -78,7 +79,7 @@ prevent unintended disclosure of sensitive private data when you publish example
|
|||||||
of your configuration in the project issues or in the Internet.
|
of your configuration in the project issues or in the Internet.
|
||||||
|
|
||||||
See more details on this technique with examples in the documentation page on
|
See more details on this technique with examples in the documentation page on
|
||||||
[configuration](bot-configuration.md).
|
[configuration](configuration.md).
|
||||||
|
|
||||||
### How to use **--strategy**?
|
### How to use **--strategy**?
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**.
|
|||||||
| `strategy` | DefaultStrategy | Defines Strategy class to use.
|
| `strategy` | DefaultStrategy | Defines Strategy class to use.
|
||||||
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
|
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
|
||||||
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
||||||
|
| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
|
||||||
| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
|
| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
|
||||||
|
|
||||||
### Parameters in the strategy
|
### Parameters in the strategy
|
||||||
|
@ -65,16 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot.
|
|||||||
|
|
||||||
For each open trade, the bot will send you the following message.
|
For each open trade, the bot will send you the following message.
|
||||||
|
|
||||||
> **Trade ID:** `123`
|
> **Trade ID:** `123` `(since 1 days ago)`
|
||||||
> **Current Pair:** CVC/BTC
|
> **Current Pair:** CVC/BTC
|
||||||
> **Open Since:** `1 days ago`
|
> **Open Since:** `1 days ago`
|
||||||
> **Amount:** `26.64180098`
|
> **Amount:** `26.64180098`
|
||||||
> **Open Rate:** `0.00007489`
|
> **Open Rate:** `0.00007489`
|
||||||
> **Close Rate:** `None`
|
|
||||||
> **Current Rate:** `0.00007489`
|
> **Current Rate:** `0.00007489`
|
||||||
> **Close Profit:** `None`
|
|
||||||
> **Current Profit:** `12.95%`
|
> **Current Profit:** `12.95%`
|
||||||
> **Open Order:** `None`
|
> **Stoploss:** `0.00007389 (-0.02%)`
|
||||||
|
|
||||||
### /status table
|
### /status table
|
||||||
|
|
||||||
|
@ -71,6 +71,13 @@ class Arguments(object):
|
|||||||
dest='loglevel',
|
dest='loglevel',
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
self.parser.add_argument(
|
||||||
|
'--logfile',
|
||||||
|
help='Log to the file specified',
|
||||||
|
dest='logfile',
|
||||||
|
type=str,
|
||||||
|
metavar='FILE'
|
||||||
|
)
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'--version',
|
'--version',
|
||||||
action='version',
|
action='version',
|
||||||
|
@ -4,16 +4,18 @@ This module contains the configuration class
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from typing import Any, Dict, Optional
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
from jsonschema import Draft4Validator, validate
|
from jsonschema import Draft4Validator, validate
|
||||||
from jsonschema.exceptions import ValidationError, best_match
|
from jsonschema.exceptions import ValidationError, best_match
|
||||||
|
|
||||||
from freqtrade import OperationalException, constants
|
from freqtrade import OperationalException, constants
|
||||||
from freqtrade.state import RunMode
|
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts
|
||||||
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -116,9 +118,22 @@ class Configuration(object):
|
|||||||
config.update({'verbosity': self.args.loglevel})
|
config.update({'verbosity': self.args.loglevel})
|
||||||
else:
|
else:
|
||||||
config.update({'verbosity': 0})
|
config.update({'verbosity': 0})
|
||||||
|
|
||||||
|
# Log to stdout, not stderr
|
||||||
|
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)]
|
||||||
|
if 'logfile' in self.args and self.args.logfile:
|
||||||
|
config.update({'logfile': self.args.logfile})
|
||||||
|
|
||||||
|
# Allow setting this as either configuration or argument
|
||||||
|
if 'logfile' in config:
|
||||||
|
log_handlers.append(RotatingFileHandler(config['logfile'],
|
||||||
|
maxBytes=1024 * 1024, # 1Mb
|
||||||
|
backupCount=10))
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
|
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=log_handlers
|
||||||
)
|
)
|
||||||
set_loggers(config['verbosity'])
|
set_loggers(config['verbosity'])
|
||||||
logger.info('Verbosity set to %s', config['verbosity'])
|
logger.info('Verbosity set to %s', config['verbosity'])
|
||||||
|
@ -367,7 +367,6 @@ class FreqtradeBot(object):
|
|||||||
stake_amount = order['cost']
|
stake_amount = order['cost']
|
||||||
amount = order['amount']
|
amount = order['amount']
|
||||||
buy_limit_filled_price = order['price']
|
buy_limit_filled_price = order['price']
|
||||||
order_id = None
|
|
||||||
|
|
||||||
self.rpc.send_msg({
|
self.rpc.send_msg({
|
||||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||||
@ -396,6 +395,10 @@ class FreqtradeBot(object):
|
|||||||
ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
|
ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update fees if order is closed
|
||||||
|
if order_status == 'closed':
|
||||||
|
self.update_trade_state(trade, order)
|
||||||
|
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
@ -426,24 +429,7 @@ class FreqtradeBot(object):
|
|||||||
:return: True if executed
|
:return: True if executed
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get order details for actual price per unit
|
self.update_trade_state(trade)
|
||||||
if trade.open_order_id:
|
|
||||||
# Update trade with order values
|
|
||||||
logger.info('Found open order for %s', trade)
|
|
||||||
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
|
||||||
# Try update amount (binance-fix)
|
|
||||||
try:
|
|
||||||
new_amount = self.get_real_amount(trade, order)
|
|
||||||
if order['amount'] != new_amount:
|
|
||||||
order['amount'] = new_amount
|
|
||||||
# Fee was applied, so set to 0
|
|
||||||
trade.fee_open = 0
|
|
||||||
|
|
||||||
except OperationalException as exception:
|
|
||||||
logger.warning("Could not update trade amount: %s", exception)
|
|
||||||
|
|
||||||
# This handles both buy and sell orders!
|
|
||||||
trade.update(order)
|
|
||||||
|
|
||||||
if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
|
if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
|
||||||
result = self.handle_stoploss_on_exchange(trade)
|
result = self.handle_stoploss_on_exchange(trade)
|
||||||
@ -508,6 +494,28 @@ class FreqtradeBot(object):
|
|||||||
f"(from {order_amount} to {real_amount}) from Trades")
|
f"(from {order_amount} to {real_amount}) from Trades")
|
||||||
return real_amount
|
return real_amount
|
||||||
|
|
||||||
|
def update_trade_state(self, trade, action_order: dict = None):
|
||||||
|
"""
|
||||||
|
Checks trades with open orders and updates the amount if necessary
|
||||||
|
"""
|
||||||
|
# Get order details for actual price per unit
|
||||||
|
if trade.open_order_id:
|
||||||
|
# Update trade with order values
|
||||||
|
logger.info('Found open order for %s', trade)
|
||||||
|
order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
|
# Try update amount (binance-fix)
|
||||||
|
try:
|
||||||
|
new_amount = self.get_real_amount(trade, order)
|
||||||
|
if order['amount'] != new_amount:
|
||||||
|
order['amount'] = new_amount
|
||||||
|
# Fee was applied, so set to 0
|
||||||
|
trade.fee_open = 0
|
||||||
|
|
||||||
|
except OperationalException as exception:
|
||||||
|
logger.warning("Could not update trade amount: %s", exception)
|
||||||
|
|
||||||
|
trade.update(order)
|
||||||
|
|
||||||
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
||||||
"""
|
"""
|
||||||
Get sell rate - either using get-ticker bid or first bid based on orderbook
|
Get sell rate - either using get-ticker bid or first bid based on orderbook
|
||||||
@ -578,7 +586,7 @@ class FreqtradeBot(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
result = False
|
result = False
|
||||||
|
try:
|
||||||
# If trade is open and the buy order is fulfilled but there is no stoploss,
|
# If trade is open and the buy order is fulfilled but there is no stoploss,
|
||||||
# then we add a stoploss on exchange
|
# then we add a stoploss on exchange
|
||||||
if not trade.open_order_id and not trade.stoploss_order_id:
|
if not trade.open_order_id and not trade.stoploss_order_id:
|
||||||
@ -614,7 +622,8 @@ class FreqtradeBot(object):
|
|||||||
# in which case we cancel stoploss order and put another one with new
|
# in which case we cancel stoploss order and put another one with new
|
||||||
# value immediately
|
# value immediately
|
||||||
self.handle_trailing_stoploss_on_exchange(trade, order)
|
self.handle_trailing_stoploss_on_exchange(trade, order)
|
||||||
|
except DependencyException as exception:
|
||||||
|
logger.warning('Unable to create stoploss order: %s', exception)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
|
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
|
||||||
|
@ -83,7 +83,7 @@ def check_migrate(engine) -> None:
|
|||||||
logger.debug(f'trying {table_back_name}')
|
logger.debug(f'trying {table_back_name}')
|
||||||
|
|
||||||
# Check for latest column
|
# Check for latest column
|
||||||
if not has_column(cols, 'stoploss_last_update'):
|
if not has_column(cols, 'stop_loss_pct'):
|
||||||
logger.info(f'Running database migration - backup available as {table_back_name}')
|
logger.info(f'Running database migration - backup available as {table_back_name}')
|
||||||
|
|
||||||
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
||||||
@ -91,10 +91,13 @@ def check_migrate(engine) -> None:
|
|||||||
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
|
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
|
||||||
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
|
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
|
||||||
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
|
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
|
||||||
|
stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null')
|
||||||
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
|
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
|
||||||
|
initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null')
|
||||||
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
|
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
|
||||||
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
|
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
|
||||||
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
||||||
|
min_rate = get_column_def(cols, 'min_rate', 'null')
|
||||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||||
strategy = get_column_def(cols, 'strategy', 'null')
|
strategy = get_column_def(cols, 'strategy', 'null')
|
||||||
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
|
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
|
||||||
@ -112,8 +115,9 @@ def check_migrate(engine) -> None:
|
|||||||
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
|
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
|
||||||
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
||||||
stake_amount, amount, open_date, close_date, open_order_id,
|
stake_amount, amount, open_date, close_date, open_order_id,
|
||||||
stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
|
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||||
max_rate, sell_reason, strategy,
|
stoploss_order_id, stoploss_last_update,
|
||||||
|
max_rate, min_rate, sell_reason, strategy,
|
||||||
ticker_interval
|
ticker_interval
|
||||||
)
|
)
|
||||||
select id, lower(exchange),
|
select id, lower(exchange),
|
||||||
@ -128,9 +132,11 @@ def check_migrate(engine) -> None:
|
|||||||
open_rate, {open_rate_requested} open_rate_requested, close_rate,
|
open_rate, {open_rate_requested} open_rate_requested, close_rate,
|
||||||
{close_rate_requested} close_rate_requested, close_profit,
|
{close_rate_requested} close_rate_requested, close_profit,
|
||||||
stake_amount, amount, open_date, close_date, open_order_id,
|
stake_amount, amount, open_date, close_date, open_order_id,
|
||||||
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
|
{stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct,
|
||||||
|
{initial_stop_loss} initial_stop_loss,
|
||||||
|
{initial_stop_loss_pct} initial_stop_loss_pct,
|
||||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||||
{max_rate} max_rate, {sell_reason} sell_reason,
|
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||||
{strategy} strategy, {ticker_interval} ticker_interval
|
{strategy} strategy, {ticker_interval} ticker_interval
|
||||||
from {table_back_name}
|
from {table_back_name}
|
||||||
""")
|
""")
|
||||||
@ -183,14 +189,20 @@ class Trade(_DECL_BASE):
|
|||||||
open_order_id = Column(String)
|
open_order_id = Column(String)
|
||||||
# absolute value of the stop loss
|
# absolute value of the stop loss
|
||||||
stop_loss = Column(Float, nullable=True, default=0.0)
|
stop_loss = Column(Float, nullable=True, default=0.0)
|
||||||
|
# percentage value of the stop loss
|
||||||
|
stop_loss_pct = Column(Float, nullable=True)
|
||||||
# absolute value of the initial stop loss
|
# absolute value of the initial stop loss
|
||||||
initial_stop_loss = Column(Float, nullable=True, default=0.0)
|
initial_stop_loss = Column(Float, nullable=True, default=0.0)
|
||||||
|
# percentage value of the initial stop loss
|
||||||
|
initial_stop_loss_pct = Column(Float, nullable=True)
|
||||||
# stoploss order id which is on exchange
|
# stoploss order id which is on exchange
|
||||||
stoploss_order_id = Column(String, nullable=True, index=True)
|
stoploss_order_id = Column(String, nullable=True, index=True)
|
||||||
# last update time of the stoploss order on exchange
|
# last update time of the stoploss order on exchange
|
||||||
stoploss_last_update = Column(DateTime, nullable=True)
|
stoploss_last_update = Column(DateTime, nullable=True)
|
||||||
# absolute value of the highest reached price
|
# absolute value of the highest reached price
|
||||||
max_rate = Column(Float, nullable=True, default=0.0)
|
max_rate = Column(Float, nullable=True, default=0.0)
|
||||||
|
# Lowest price reached
|
||||||
|
min_rate = Column(Float, nullable=True)
|
||||||
sell_reason = Column(String, nullable=True)
|
sell_reason = Column(String, nullable=True)
|
||||||
strategy = Column(String, nullable=True)
|
strategy = Column(String, nullable=True)
|
||||||
ticker_interval = Column(Integer, nullable=True)
|
ticker_interval = Column(Integer, nullable=True)
|
||||||
@ -201,8 +213,22 @@ class Trade(_DECL_BASE):
|
|||||||
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
||||||
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
||||||
|
|
||||||
|
def adjust_min_max_rates(self, current_price: float):
|
||||||
|
"""
|
||||||
|
Adjust the max_rate and min_rate.
|
||||||
|
"""
|
||||||
|
logger.debug("Adjusting min/max rates")
|
||||||
|
self.max_rate = max(current_price, self.max_rate or self.open_rate)
|
||||||
|
self.min_rate = min(current_price, self.min_rate or self.open_rate)
|
||||||
|
|
||||||
def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False):
|
def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False):
|
||||||
"""this adjusts the stop loss to it's most recently observed setting"""
|
"""
|
||||||
|
This adjusts the stop loss to it's most recently observed setting
|
||||||
|
:param current_price: Current rate the asset is traded
|
||||||
|
:param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price).
|
||||||
|
:param initial: Called to initiate stop_loss.
|
||||||
|
Skips everything if self.stop_loss is already set.
|
||||||
|
"""
|
||||||
|
|
||||||
if initial and not (self.stop_loss is None or self.stop_loss == 0):
|
if initial and not (self.stop_loss is None or self.stop_loss == 0):
|
||||||
# Don't modify if called with initial and nothing to do
|
# Don't modify if called with initial and nothing to do
|
||||||
@ -210,24 +236,20 @@ class Trade(_DECL_BASE):
|
|||||||
|
|
||||||
new_loss = float(current_price * (1 - abs(stoploss)))
|
new_loss = float(current_price * (1 - abs(stoploss)))
|
||||||
|
|
||||||
# keeping track of the highest observed rate for this trade
|
|
||||||
if self.max_rate is None:
|
|
||||||
self.max_rate = current_price
|
|
||||||
else:
|
|
||||||
if current_price > self.max_rate:
|
|
||||||
self.max_rate = current_price
|
|
||||||
|
|
||||||
# no stop loss assigned yet
|
# no stop loss assigned yet
|
||||||
if not self.stop_loss:
|
if not self.stop_loss:
|
||||||
logger.debug("assigning new stop loss")
|
logger.debug("assigning new stop loss")
|
||||||
self.stop_loss = new_loss
|
self.stop_loss = new_loss
|
||||||
|
self.stop_loss_pct = -1 * abs(stoploss)
|
||||||
self.initial_stop_loss = new_loss
|
self.initial_stop_loss = new_loss
|
||||||
|
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
|
|
||||||
# evaluate if the stop loss needs to be updated
|
# evaluate if the stop loss needs to be updated
|
||||||
else:
|
else:
|
||||||
if new_loss > self.stop_loss: # stop losses only walk up, never down!
|
if new_loss > self.stop_loss: # stop losses only walk up, never down!
|
||||||
self.stop_loss = new_loss
|
self.stop_loss = new_loss
|
||||||
|
self.stop_loss_pct = -1 * abs(stoploss)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
logger.debug("adjusted stop loss")
|
logger.debug("adjusted stop loss")
|
||||||
else:
|
else:
|
||||||
|
@ -110,6 +110,10 @@ class RPC(object):
|
|||||||
amount=round(trade.amount, 8),
|
amount=round(trade.amount, 8),
|
||||||
close_profit=fmt_close_profit,
|
close_profit=fmt_close_profit,
|
||||||
current_profit=round(current_profit * 100, 2),
|
current_profit=round(current_profit * 100, 2),
|
||||||
|
stop_loss=trade.stop_loss,
|
||||||
|
stop_loss_pct=trade.stop_loss_pct,
|
||||||
|
initial_stop_loss=trade.initial_stop_loss,
|
||||||
|
initial_stop_loss_pct=trade.initial_stop_loss_pct,
|
||||||
open_order='({} {} rem={:.8f})'.format(
|
open_order='({} {} rem={:.8f})'.format(
|
||||||
order['type'], order['side'], order['remaining']
|
order['type'], order['side'], order['remaining']
|
||||||
) if order else None,
|
) if order else None,
|
||||||
|
@ -194,21 +194,34 @@ class Telegram(RPC):
|
|||||||
for result in results:
|
for result in results:
|
||||||
result['date'] = result['date'].humanize()
|
result['date'] = result['date'].humanize()
|
||||||
|
|
||||||
messages = [
|
messages = []
|
||||||
"*Trade ID:* `{trade_id}`\n"
|
for r in results:
|
||||||
"*Current Pair:* {pair}\n"
|
lines = [
|
||||||
"*Open Since:* `{date}`\n"
|
"*Trade ID:* `{trade_id}` `(since {date})`",
|
||||||
"*Amount:* `{amount}`\n"
|
"*Current Pair:* {pair}",
|
||||||
"*Open Rate:* `{open_rate:.8f}`\n"
|
"*Amount:* `{amount}`",
|
||||||
"*Close Rate:* `{close_rate}`\n"
|
"*Open Rate:* `{open_rate:.8f}`",
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
|
||||||
"*Close Profit:* `{close_profit}`\n"
|
"*Current Rate:* `{current_rate:.8f}`",
|
||||||
"*Current Profit:* `{current_profit:.2f}%`\n"
|
"*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
|
||||||
"*Open Order:* `{open_order}`".format(**result)
|
"*Current Profit:* `{current_profit:.2f}%`",
|
||||||
for result in results
|
|
||||||
|
# Adding initial stoploss only if it is different from stoploss
|
||||||
|
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
|
||||||
|
("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "")
|
||||||
|
if r['stop_loss'] != r['initial_stop_loss'] else "",
|
||||||
|
|
||||||
|
# Adding stoploss and stoploss percentage only if it is not None
|
||||||
|
"*Stoploss:* `{stop_loss:.8f}` " +
|
||||||
|
("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
|
||||||
|
|
||||||
|
"*Open Order:* `{open_order}`" if r['open_order'] else ""
|
||||||
]
|
]
|
||||||
|
messages.append("\n".join(filter(None, lines)).format(**r))
|
||||||
|
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
self._send_msg(msg, bot=bot)
|
self._send_msg(msg, bot=bot)
|
||||||
|
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
self._send_msg(str(e), bot=bot)
|
self._send_msg(str(e), bot=bot)
|
||||||
|
|
||||||
|
@ -247,6 +247,9 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
This function evaluate if on the condition required to trigger a sell has been reached
|
This function evaluate if on the condition required to trigger a sell has been reached
|
||||||
if the threshold is reached and updates the trade record.
|
if the threshold is reached and updates the trade record.
|
||||||
|
:param low: Only used during backtesting to simulate stoploss
|
||||||
|
:param high: Only used during backtesting, to simulate ROI
|
||||||
|
:param force_stoploss: Externally provided stoploss
|
||||||
:return: True if trade should be sold, False otherwise
|
:return: True if trade should be sold, False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -254,14 +257,16 @@ class IStrategy(ABC):
|
|||||||
current_rate = low or rate
|
current_rate = low or rate
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
|
|
||||||
|
trade.adjust_min_max_rates(high or current_rate)
|
||||||
|
|
||||||
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
||||||
current_time=date, current_profit=current_profit,
|
current_time=date, current_profit=current_profit,
|
||||||
force_stoploss=force_stoploss)
|
force_stoploss=force_stoploss, high=high)
|
||||||
|
|
||||||
if stoplossflag.sell_flag:
|
if stoplossflag.sell_flag:
|
||||||
return stoplossflag
|
return stoplossflag
|
||||||
|
|
||||||
# Set current rate to low for backtesting sell
|
# Set current rate to high for backtesting sell
|
||||||
current_rate = high or rate
|
current_rate = high or rate
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
experimental = self.config.get('experimental', {})
|
experimental = self.config.get('experimental', {})
|
||||||
@ -285,8 +290,9 @@ class IStrategy(ABC):
|
|||||||
|
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
||||||
|
|
||||||
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
|
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
||||||
current_profit: float, force_stoploss: float) -> SellCheckTuple:
|
current_time: datetime, current_profit: float,
|
||||||
|
force_stoploss: float, high: float = None) -> SellCheckTuple:
|
||||||
"""
|
"""
|
||||||
Based on current profit of the trade and configured (trailing) stoploss,
|
Based on current profit of the trade and configured (trailing) stoploss,
|
||||||
decides to sell or not
|
decides to sell or not
|
||||||
@ -294,13 +300,33 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
trailing_stop = self.config.get('trailing_stop', False)
|
trailing_stop = self.config.get('trailing_stop', False)
|
||||||
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
|
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
||||||
else self.stoploss, initial=True)
|
|
||||||
|
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
|
||||||
|
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
||||||
|
|
||||||
|
if trailing_stop:
|
||||||
|
# trailing stoploss handling
|
||||||
|
|
||||||
|
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
|
||||||
|
tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False)
|
||||||
|
|
||||||
|
# Don't update stoploss if trailing_only_offset_is_reached is true.
|
||||||
|
if not (tsl_only_offset and current_profit < sl_offset):
|
||||||
|
# Specific handling for trailing_stop_positive
|
||||||
|
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
|
||||||
|
# Ignore mypy error check in configuration that this is a float
|
||||||
|
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
||||||
|
logger.debug(f"using positive stop loss: {stop_loss_value} "
|
||||||
|
f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%")
|
||||||
|
|
||||||
|
trade.adjust_stop_loss(high or current_rate, stop_loss_value)
|
||||||
|
|
||||||
# evaluate if the stoploss was hit if stoploss is not on exchange
|
# evaluate if the stoploss was hit if stoploss is not on exchange
|
||||||
if ((self.stoploss is not None) and
|
if ((self.stoploss is not None) and
|
||||||
(trade.stop_loss >= current_rate) and
|
(trade.stop_loss >= current_rate) and
|
||||||
(not self.order_types.get('stoploss_on_exchange'))):
|
(not self.order_types.get('stoploss_on_exchange'))):
|
||||||
|
|
||||||
selltype = SellType.STOP_LOSS
|
selltype = SellType.STOP_LOSS
|
||||||
# If Trailing stop (and max-rate did move above open rate)
|
# If Trailing stop (and max-rate did move above open rate)
|
||||||
if trailing_stop and trade.open_rate != trade.max_rate:
|
if trailing_stop and trade.open_rate != trade.max_rate:
|
||||||
@ -315,29 +341,6 @@ class IStrategy(ABC):
|
|||||||
logger.debug('Stop loss hit.')
|
logger.debug('Stop loss hit.')
|
||||||
return SellCheckTuple(sell_flag=True, sell_type=selltype)
|
return SellCheckTuple(sell_flag=True, sell_type=selltype)
|
||||||
|
|
||||||
# update the stop loss afterwards, after all by definition it's supposed to be hanging
|
|
||||||
if trailing_stop:
|
|
||||||
|
|
||||||
# check if we have a special stop loss for positive condition
|
|
||||||
# and if profit is positive
|
|
||||||
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
|
||||||
|
|
||||||
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
|
|
||||||
|
|
||||||
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
|
|
||||||
|
|
||||||
# Ignore mypy error check in configuration that this is a float
|
|
||||||
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
|
||||||
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
|
|
||||||
f"with offset {sl_offset:.4g} "
|
|
||||||
f"since we have profit {current_profit:.4f}%")
|
|
||||||
|
|
||||||
# if trailing_only_offset_is_reached is true,
|
|
||||||
# we update trailing stoploss only if offset is reached.
|
|
||||||
tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False)
|
|
||||||
if not (tsl_only_offset and current_profit < sl_offset):
|
|
||||||
trade.adjust_stop_loss(current_rate, stop_loss_value)
|
|
||||||
|
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
||||||
|
|
||||||
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
|
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
|
||||||
|
@ -4,7 +4,6 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Dict, Optional
|
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
@ -13,11 +12,11 @@ from telegram import Chat, Message, Update
|
|||||||
|
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange
|
|
||||||
from freqtrade.edge import Edge, PairInfo
|
from freqtrade.edge import Edge, PairInfo
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
from freqtrade.worker import Worker
|
||||||
|
|
||||||
logging.getLogger('').setLevel(logging.INFO)
|
logging.getLogger('').setLevel(logging.INFO)
|
||||||
|
|
||||||
@ -96,7 +95,6 @@ def patch_freqtradebot(mocker, config) -> None:
|
|||||||
:param config: Config to pass to the bot
|
:param config: Config to pass to the bot
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
patch_coinmarketcap(mocker, {'price_usd': 12345.0})
|
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
patch_exchange(mocker, None)
|
patch_exchange(mocker, None)
|
||||||
@ -114,7 +112,8 @@ def get_patched_worker(mocker, config) -> Worker:
|
|||||||
return Worker(args=None, config=config)
|
return Worker(args=None, config=config)
|
||||||
|
|
||||||
|
|
||||||
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_coinmarketcap(mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Mocker to coinmarketcap to speed up tests
|
Mocker to coinmarketcap to speed up tests
|
||||||
:param mocker: mocker to patch coinmarketcap class
|
:param mocker: mocker to patch coinmarketcap class
|
||||||
|
@ -28,6 +28,7 @@ class BTContainer(NamedTuple):
|
|||||||
roi: float
|
roi: float
|
||||||
trades: List[BTrade]
|
trades: List[BTrade]
|
||||||
profit_perc: float
|
profit_perc: float
|
||||||
|
trailing_stop: bool = False
|
||||||
|
|
||||||
|
|
||||||
def _get_frame_time_from_offset(offset):
|
def _get_frame_time_from_offset(offset):
|
||||||
|
@ -14,10 +14,10 @@ from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataf
|
|||||||
from freqtrade.tests.conftest import patch_exchange
|
from freqtrade.tests.conftest import patch_exchange
|
||||||
|
|
||||||
|
|
||||||
# Test 0 Minus 8% Close
|
# Test 1 Minus 8% Close
|
||||||
# Test with Stop-loss at 1%
|
# Test with Stop-loss at 1%
|
||||||
# TC1: Stop-Loss Triggered 1% loss
|
# TC1: Stop-Loss Triggered 1% loss
|
||||||
tc0 = BTContainer(data=[
|
tc1 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -30,10 +30,10 @@ tc0 = BTContainer(data=[
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Test 1 Minus 4% Low, minus 1% close
|
# Test 2 Minus 4% Low, minus 1% close
|
||||||
# Test with Stop-Loss at 3%
|
# Test with Stop-Loss at 3%
|
||||||
# TC2: Stop-Loss Triggered 3% Loss
|
# TC2: Stop-Loss Triggered 3% Loss
|
||||||
tc1 = BTContainer(data=[
|
tc2 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -49,11 +49,10 @@ tc1 = BTContainer(data=[
|
|||||||
# Test 3 Candle drops 4%, Recovers 1%.
|
# Test 3 Candle drops 4%, Recovers 1%.
|
||||||
# Entry Criteria Met
|
# Entry Criteria Met
|
||||||
# Candle drops 20%
|
# Candle drops 20%
|
||||||
# Candle Data for test 3
|
|
||||||
# Test with Stop-Loss at 2%
|
# Test with Stop-Loss at 2%
|
||||||
# TC3: Trade-A: Stop-Loss Triggered 2% Loss
|
# TC3: Trade-A: Stop-Loss Triggered 2% Loss
|
||||||
# Trade-B: Stop-Loss Triggered 2% Loss
|
# Trade-B: Stop-Loss Triggered 2% Loss
|
||||||
tc2 = BTContainer(data=[
|
tc3 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -71,7 +70,7 @@ tc2 = BTContainer(data=[
|
|||||||
# Candle Data for test 3 – Candle drops 3% Closed 15% up
|
# Candle Data for test 3 – Candle drops 3% Closed 15% up
|
||||||
# Test with Stop-loss at 2% ROI 6%
|
# Test with Stop-loss at 2% ROI 6%
|
||||||
# TC4: Stop-Loss Triggered 2% Loss
|
# TC4: Stop-Loss Triggered 2% Loss
|
||||||
tc3 = BTContainer(data=[
|
tc4 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -83,10 +82,10 @@ tc3 = BTContainer(data=[
|
|||||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
|
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 4 / Drops 0.5% Closes +20%
|
# Test 5 / Drops 0.5% Closes +20%
|
||||||
# Set stop-loss at 1% ROI 3%
|
# Set stop-loss at 1% ROI 3%
|
||||||
# TC5: ROI triggers 3% Gain
|
# TC5: ROI triggers 3% Gain
|
||||||
tc4 = BTContainer(data=[
|
tc5 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -99,10 +98,9 @@ tc4 = BTContainer(data=[
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve
|
# Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve
|
||||||
# Candle Data for test 6
|
|
||||||
# Set stop-loss at 2% ROI at 5%
|
# Set stop-loss at 2% ROI at 5%
|
||||||
# TC6: Stop-Loss triggers 2% Loss
|
# TC6: Stop-Loss triggers 2% Loss
|
||||||
tc5 = BTContainer(data=[
|
tc6 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||||
@ -115,10 +113,9 @@ tc5 = BTContainer(data=[
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Test 7 - 6% Positive / 1% Negative / Close 1% Positve
|
# Test 7 - 6% Positive / 1% Negative / Close 1% Positve
|
||||||
# Candle Data for test 7
|
|
||||||
# Set stop-loss at 2% ROI at 3%
|
# Set stop-loss at 2% ROI at 3%
|
||||||
# TC7: ROI Triggers 3% Gain
|
# TC7: ROI Triggers 3% Gain
|
||||||
tc6 = BTContainer(data=[
|
tc7 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||||
@ -130,14 +127,47 @@ tc6 = BTContainer(data=[
|
|||||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Test 8 - trailing_stop should raise so candle 3 causes a stoploss.
|
||||||
|
# Set stop-loss at 10%, ROI at 10% (should not apply)
|
||||||
|
# TC8: Trailing stoploss - stoploss should be adjusted candle 2
|
||||||
|
tc8 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||||
|
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
|
||||||
|
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
|
||||||
|
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||||
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
|
stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True,
|
||||||
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Test 9 - trailing_stop should raise - high and low in same candle.
|
||||||
|
# Candle Data for test 9
|
||||||
|
# Set stop-loss at 10%, ROI at 10% (should not apply)
|
||||||
|
# TC9: Trailing stoploss - stoploss should be adjusted candle 2
|
||||||
|
tc9 = BTContainer(data=[
|
||||||
|
# D O H L C V B S
|
||||||
|
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||||
|
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
|
||||||
|
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
|
||||||
|
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
|
||||||
|
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||||
|
stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True,
|
||||||
|
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||||
|
)
|
||||||
|
|
||||||
TESTS = [
|
TESTS = [
|
||||||
tc0,
|
|
||||||
tc1,
|
tc1,
|
||||||
tc2,
|
tc2,
|
||||||
tc3,
|
tc3,
|
||||||
tc4,
|
tc4,
|
||||||
tc5,
|
tc5,
|
||||||
tc6,
|
tc6,
|
||||||
|
tc7,
|
||||||
|
tc8,
|
||||||
|
tc9,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -148,8 +178,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
|||||||
"""
|
"""
|
||||||
default_conf["stoploss"] = data.stop_loss
|
default_conf["stoploss"] = data.stop_loss
|
||||||
default_conf["minimal_roi"] = {"0": data.roi}
|
default_conf["minimal_roi"] = {"0": data.roi}
|
||||||
default_conf['ticker_interval'] = tests_ticker_interval
|
default_conf["ticker_interval"] = tests_ticker_interval
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0))
|
default_conf["trailing_stop"] = data.trailing_stop
|
||||||
|
mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0))
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
frame = _build_backtest_dataframe(data.data)
|
frame = _build_backtest_dataframe(data.data)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
@ -157,7 +188,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
|||||||
backtesting.advise_sell = lambda a, m: frame
|
backtesting.advise_sell = lambda a, m: frame
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
pair = 'UNITTEST/BTC'
|
pair = "UNITTEST/BTC"
|
||||||
# Dummy data as we mock the analyze functions
|
# Dummy data as we mock the analyze functions
|
||||||
data_processed = {pair: DataFrame()}
|
data_processed = {pair: DataFrame()}
|
||||||
min_date, max_date = get_timeframe({pair: frame})
|
min_date, max_date = get_timeframe({pair: frame})
|
||||||
|
@ -8,7 +8,7 @@ import pytest
|
|||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||||
from freqtrade.tests.conftest import log_has, patch_coinmarketcap
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
|
|
||||||
def test_pair_convertion_object():
|
def test_pair_convertion_object():
|
||||||
@ -40,7 +40,6 @@ def test_pair_convertion_object():
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_is_supported(mocker):
|
def test_fiat_convert_is_supported(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
||||||
assert fiat_convert._is_supported_fiat(fiat='usd') is True
|
assert fiat_convert._is_supported_fiat(fiat='usd') is True
|
||||||
@ -49,7 +48,6 @@ def test_fiat_convert_is_supported(mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_add_pair(mocker):
|
def test_fiat_convert_add_pair(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_find_price(mocker):
|
def test_fiat_convert_find_price(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||||
@ -93,15 +89,12 @@ def test_fiat_convert_find_price(mocker):
|
|||||||
|
|
||||||
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
||||||
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
|
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_get_price(mocker):
|
def test_fiat_convert_get_price(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
|
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||||
return_value=28000.0)
|
return_value=28000.0)
|
||||||
|
|
||||||
@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_same_currencies(mocker):
|
def test_fiat_convert_same_currencies(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
|
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_two_FIAT(mocker):
|
def test_fiat_convert_two_FIAT(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
|
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
|
||||||
|
|
||||||
|
|
||||||
def test_loadcryptomap(mocker):
|
def test_loadcryptomap(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert len(fiat_convert._cryptomap) == 2
|
assert len(fiat_convert._cryptomap) == 2
|
||||||
@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker):
|
|||||||
|
|
||||||
def test_fiat_convert_without_network(mocker):
|
def test_fiat_convert_without_network(mocker):
|
||||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_convert_amount(mocker):
|
def test_convert_amount(mocker):
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
@ -14,7 +14,7 @@ from freqtrade.persistence import Trade
|
|||||||
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 freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
|
from freqtrade.tests.conftest import patch_exchange
|
||||||
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float:
|
|||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
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',
|
||||||
@ -60,6 +59,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
'amount': 90.99181074,
|
'amount': 90.99181074,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'current_profit': -0.59,
|
'current_profit': -0.59,
|
||||||
|
'stop_loss': 0.0,
|
||||||
|
'initial_stop_loss': 0.0,
|
||||||
|
'initial_stop_loss_pct': None,
|
||||||
|
'stop_loss_pct': None,
|
||||||
'open_order': '(limit buy rem=0.00000000)'
|
'open_order': '(limit buy rem=0.00000000)'
|
||||||
} == results[0]
|
} == results[0]
|
||||||
|
|
||||||
@ -80,12 +83,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
'amount': 90.99181074,
|
'amount': 90.99181074,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'current_profit': ANY,
|
'current_profit': ANY,
|
||||||
|
'stop_loss': 0.0,
|
||||||
|
'initial_stop_loss': 0.0,
|
||||||
|
'initial_stop_loss_pct': None,
|
||||||
|
'stop_loss_pct': None,
|
||||||
'open_order': '(limit buy rem=0.00000000)'
|
'open_order': '(limit buy rem=0.00000000)'
|
||||||
} == results[0]
|
} == results[0]
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -122,7 +128,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
|
|
||||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -176,7 +181,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
'freqtrade.rpc.fiat_convert.Market',
|
'freqtrade.rpc.fiat_convert.Market',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
)
|
)
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=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())
|
||||||
@ -336,7 +340,6 @@ def test_rpc_balance_handle(default_conf, mocker):
|
|||||||
'freqtrade.rpc.fiat_convert.Market',
|
'freqtrade.rpc.fiat_convert.Market',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
)
|
)
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=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())
|
||||||
@ -367,7 +370,6 @@ def test_rpc_balance_handle(default_conf, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_start(mocker, default_conf) -> None:
|
def test_rpc_start(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -391,7 +393,6 @@ def test_rpc_start(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_stop(mocker, default_conf) -> None:
|
def test_rpc_stop(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -416,7 +417,6 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_stopbuy(mocker, default_conf) -> None:
|
def test_rpc_stopbuy(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -437,7 +437,6 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -539,7 +538,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
|||||||
|
|
||||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, markets, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -576,7 +574,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -605,7 +602,6 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
|||||||
|
|
||||||
def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
|
def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
|
||||||
default_conf['forcebuy_enable'] = True
|
default_conf['forcebuy_enable'] = True
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
|
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||||
@ -657,7 +653,6 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
|
|||||||
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
||||||
default_conf['forcebuy_enable'] = True
|
default_conf['forcebuy_enable'] = True
|
||||||
default_conf['initial_state'] = 'stopped'
|
default_conf['initial_state'] = 'stopped'
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -671,7 +666,6 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -685,7 +679,6 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_whitelist(mocker, default_conf) -> None:
|
def test_rpc_whitelist(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -698,7 +691,6 @@ def test_rpc_whitelist(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
|
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
default_conf['pairlist'] = {'method': 'VolumePairList',
|
default_conf['pairlist'] = {'method': 'VolumePairList',
|
||||||
'config': {'number_assets': 4}
|
'config': {'number_assets': 4}
|
||||||
@ -716,7 +708,6 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_blacklist(mocker, default_conf) -> None:
|
def test_rpc_blacklist(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -736,7 +727,6 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -746,7 +736,6 @@ def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
|
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
|
@ -20,8 +20,7 @@ from freqtrade.rpc import RPCMessageType
|
|||||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.tests.conftest import (get_patched_freqtradebot,
|
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange)
|
||||||
log_has, patch_coinmarketcap, patch_exchange)
|
|
||||||
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
@ -90,7 +89,6 @@ def test_cleanup(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only(default_conf, mocker, caplog) -> None:
|
def test_authorized_only(default_conf, mocker, caplog) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker, None)
|
patch_exchange(mocker, None)
|
||||||
|
|
||||||
chat = Chat(0, 0)
|
chat = Chat(0, 0)
|
||||||
@ -120,7 +118,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker, None)
|
patch_exchange(mocker, None)
|
||||||
chat = Chat(0xdeadbeef, 0)
|
chat = Chat(0xdeadbeef, 0)
|
||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
@ -149,7 +146,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
@ -183,7 +179,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
default_conf['telegram']['chat_id'] = 123
|
default_conf['telegram']['chat_id'] = 123
|
||||||
|
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -206,6 +201,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
'amount': 90.99181074,
|
'amount': 90.99181074,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'current_profit': -0.59,
|
'current_profit': -0.59,
|
||||||
|
'initial_stop_loss': 1.098e-05,
|
||||||
|
'stop_loss': 1.099e-05,
|
||||||
|
'initial_stop_loss_pct': -0.05,
|
||||||
|
'stop_loss_pct': -0.01,
|
||||||
'open_order': '(limit buy rem=0.00000000)'
|
'open_order': '(limit buy rem=0.00000000)'
|
||||||
}]),
|
}]),
|
||||||
_status_table=status_table,
|
_status_table=status_table,
|
||||||
@ -232,7 +231,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -274,12 +272,18 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
|
|||||||
# Trigger status while we have a fulfilled order for the open trade
|
# Trigger status while we have a fulfilled order for the open trade
|
||||||
telegram._status(bot=MagicMock(), update=update)
|
telegram._status(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
|
# close_rate should not be included in the message as the trade is not closed
|
||||||
|
# and no line should be empty
|
||||||
|
lines = msg_mock.call_args_list[0][0][0].split('\n')
|
||||||
|
assert '' not in lines
|
||||||
|
assert 'Close Rate' not in ''.join(lines)
|
||||||
|
assert 'Close Profit' not in ''.join(lines)
|
||||||
|
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
|
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -333,7 +337,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
|
|||||||
|
|
||||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, markets, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
|
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
|
||||||
@ -405,7 +408,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -442,7 +444,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
|
|
||||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -548,7 +549,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
'last': 0.1,
|
'last': 0.1,
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance)
|
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
|
||||||
|
|
||||||
@ -635,7 +635,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stop_handle(default_conf, update, mocker) -> None:
|
def test_stop_handle(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -655,7 +654,6 @@ def test_stop_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -675,7 +673,6 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stopbuy_handle(default_conf, update, mocker) -> None:
|
def test_stopbuy_handle(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -695,7 +692,6 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_reload_conf_handle(default_conf, update, mocker) -> None:
|
def test_reload_conf_handle(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -716,7 +712,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
def test_forcesell_handle(default_conf, update, ticker, fee,
|
def test_forcesell_handle(default_conf, update, ticker, fee,
|
||||||
ticker_sell_up, markets, mocker) -> None:
|
ticker_sell_up, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
@ -767,7 +762,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||||
ticker_sell_down, markets, mocker) -> None:
|
ticker_sell_down, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_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=15000.0)
|
||||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||||
@ -822,7 +816,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||||
return_value=15000.0)
|
return_value=15000.0)
|
||||||
@ -869,7 +862,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
|
|||||||
|
|
||||||
|
|
||||||
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_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=15000.0)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
@ -910,7 +902,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
|
def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
@ -948,7 +939,6 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None:
|
def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
@ -973,7 +963,6 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non
|
|||||||
|
|
||||||
def test_performance_handle(default_conf, update, ticker, fee,
|
def test_performance_handle(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1015,7 +1004,6 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1058,7 +1046,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_whitelist_static(default_conf, update, mocker) -> None:
|
def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1076,7 +1063,6 @@ def test_whitelist_static(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_whitelist_dynamic(default_conf, update, mocker) -> None:
|
def test_whitelist_dynamic(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1098,7 +1084,6 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_blacklist_static(default_conf, update, mocker) -> None:
|
def test_blacklist_static(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1123,7 +1108,6 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_edge_disabled(default_conf, update, mocker) -> None:
|
def test_edge_disabled(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1141,7 +1125,6 @@ def test_edge_disabled(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_edge_enabled(edge_conf, update, mocker) -> None:
|
def test_edge_enabled(edge_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
@ -1165,7 +1148,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_help_handle(default_conf, update, mocker) -> None:
|
def test_help_handle(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1182,7 +1164,6 @@ def test_help_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_version_handle(default_conf, update, mocker) -> None:
|
def test_version_handle(default_conf, update, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -1409,7 +1390,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test__send_msg(default_conf, mocker) -> None:
|
def test__send_msg(default_conf, mocker) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
@ -1421,7 +1401,6 @@ def test__send_msg(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
|
def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
|
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
|
||||||
|
@ -5,6 +5,7 @@ import logging
|
|||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from jsonschema import Draft4Validator, ValidationError, validate
|
from jsonschema import Draft4Validator, ValidationError, validate
|
||||||
@ -547,6 +548,23 @@ def test_set_loggers() -> None:
|
|||||||
assert logging.getLogger('telegram').level is logging.INFO
|
assert logging.getLogger('telegram').level is logging.INFO
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_logfile(default_conf, mocker):
|
||||||
|
mocker.patch('freqtrade.configuration.open',
|
||||||
|
mocker.mock_open(read_data=json.dumps(default_conf)))
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
'--logfile', 'test_file.log',
|
||||||
|
]
|
||||||
|
args = Arguments(arglist, '').get_parsed_arg()
|
||||||
|
configuration = Configuration(args)
|
||||||
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
assert validated_conf['logfile'] == "test_file.log"
|
||||||
|
f = Path("test_file.log")
|
||||||
|
assert f.is_file()
|
||||||
|
f.unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
|
def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
|
||||||
default_conf['forcebuy_enable'] = True
|
default_conf['forcebuy_enable'] = True
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
@ -1062,6 +1062,13 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
|
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.stoploss_limit',
|
||||||
|
side_effect=DependencyException()
|
||||||
|
)
|
||||||
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
|
assert log_has('Unable to create stoploss order: ', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
||||||
markets, limit_buy_order, limit_sell_order) -> None:
|
markets, limit_buy_order, limit_sell_order) -> None:
|
||||||
@ -1312,16 +1319,83 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
|
|||||||
# test amount modified by fee-logic
|
# test amount modified by fee-logic
|
||||||
assert not freqtrade.process_maybe_execute_sell(trade)
|
assert not freqtrade.process_maybe_execute_sell(trade)
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_maybe_execute_sell_exception(mocker, default_conf,
|
||||||
|
limit_buy_order, caplog) -> None:
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
||||||
|
|
||||||
|
trade = MagicMock()
|
||||||
|
trade.open_order_id = '123'
|
||||||
|
trade.open_fee = 0.001
|
||||||
|
|
||||||
|
# Test raise of DependencyException exception
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.freqtradebot.FreqtradeBot.update_trade_state',
|
||||||
|
side_effect=DependencyException()
|
||||||
|
)
|
||||||
|
freqtrade.process_maybe_execute_sell(trade)
|
||||||
|
assert log_has('Unable to sell trade: ', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||||
|
return_value=limit_buy_order['amount'])
|
||||||
|
|
||||||
|
trade = Trade()
|
||||||
|
# Mock session away
|
||||||
|
Trade.session = MagicMock()
|
||||||
|
trade.open_order_id = '123'
|
||||||
|
trade.open_fee = 0.001
|
||||||
|
freqtrade.update_trade_state(trade)
|
||||||
|
# Test amount not modified by fee-logic
|
||||||
|
assert not log_has_re(r'Applying fee to .*', caplog.record_tuples)
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.amount == limit_buy_order['amount']
|
||||||
|
|
||||||
|
trade.open_order_id = '123'
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
|
||||||
|
assert trade.amount != 90.81
|
||||||
|
# test amount modified by fee-logic
|
||||||
|
freqtrade.update_trade_state(trade)
|
||||||
|
assert trade.amount == 90.81
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
# Assert we call handle_trade() if trade is feasible for execution
|
# Assert we call handle_trade() if trade is feasible for execution
|
||||||
assert freqtrade.process_maybe_execute_sell(trade)
|
freqtrade.update_trade_state(trade)
|
||||||
|
|
||||||
regexp = re.compile('Found open order for.*')
|
regexp = re.compile('Found open order for.*')
|
||||||
assert filter(regexp.match, caplog.record_tuples)
|
assert filter(regexp.match, caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_process_maybe_execute_sell_exception(mocker, default_conf,
|
def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker):
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||||
|
# get_order should not be called!!
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
|
||||||
|
patch_exchange(mocker)
|
||||||
|
Trade.session = MagicMock()
|
||||||
|
amount = sum(x['amount'] for x in trades_for_order)
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
trade = Trade(
|
||||||
|
pair='LTC/ETH',
|
||||||
|
amount=amount,
|
||||||
|
exchange='binance',
|
||||||
|
open_rate=0.245441,
|
||||||
|
open_order_id="123456"
|
||||||
|
)
|
||||||
|
freqtrade.update_trade_state(trade, limit_buy_order)
|
||||||
|
assert trade.amount != amount
|
||||||
|
assert trade.amount == limit_buy_order['amount']
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_trade_state_exception(mocker, default_conf,
|
||||||
limit_buy_order, caplog) -> None:
|
limit_buy_order, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
||||||
@ -1335,17 +1409,9 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
|
|||||||
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||||
side_effect=OperationalException()
|
side_effect=OperationalException()
|
||||||
)
|
)
|
||||||
freqtrade.process_maybe_execute_sell(trade)
|
freqtrade.update_trade_state(trade)
|
||||||
assert log_has('Could not update trade amount: ', caplog.record_tuples)
|
assert log_has('Could not update trade amount: ', caplog.record_tuples)
|
||||||
|
|
||||||
# Test raise of DependencyException exception
|
|
||||||
mocker.patch(
|
|
||||||
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
|
||||||
side_effect=DependencyException()
|
|
||||||
)
|
|
||||||
freqtrade.process_maybe_execute_sell(trade)
|
|
||||||
assert log_has('Unable to sell trade: ', caplog.record_tuples)
|
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
||||||
fee, markets, mocker) -> None:
|
fee, markets, mocker) -> None:
|
||||||
@ -2296,9 +2362,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
|
|||||||
}
|
}
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
freqtrade.strategy.stop_loss_reached = \
|
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
|
||||||
lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple(
|
sell_flag=False, sell_type=SellType.NONE))
|
||||||
sell_flag=False, sell_type=SellType.NONE)
|
|
||||||
freqtrade.create_trade()
|
freqtrade.create_trade()
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
@ -2444,8 +2509,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss mode: 0.01 with offset 0 '
|
assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%',
|
||||||
f'since we have profit 0.2666%',
|
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
@ -2504,8 +2568,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 '
|
assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%',
|
||||||
f'since we have profit 0.2666%',
|
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
@ -2584,8 +2647,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 '
|
assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%',
|
||||||
f'since we have profit 0.1218%',
|
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||||
assert trade.stop_loss == 0.0000117705
|
assert trade.stop_loss == 0.0000117705
|
||||||
|
@ -510,6 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
|||||||
assert trade.pair == "ETC/BTC"
|
assert trade.pair == "ETC/BTC"
|
||||||
assert trade.exchange == "binance"
|
assert trade.exchange == "binance"
|
||||||
assert trade.max_rate == 0.0
|
assert trade.max_rate == 0.0
|
||||||
|
assert trade.min_rate is None
|
||||||
assert trade.stop_loss == 0.0
|
assert trade.stop_loss == 0.0
|
||||||
assert trade.initial_stop_loss == 0.0
|
assert trade.initial_stop_loss == 0.0
|
||||||
assert trade.sell_reason is None
|
assert trade.sell_reason is None
|
||||||
@ -585,7 +586,58 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
|||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):
|
def test_adjust_stop_loss(fee):
|
||||||
|
trade = Trade(
|
||||||
|
pair='ETH/BTC',
|
||||||
|
stake_amount=0.001,
|
||||||
|
fee_open=fee.return_value,
|
||||||
|
fee_close=fee.return_value,
|
||||||
|
exchange='bittrex',
|
||||||
|
open_rate=1,
|
||||||
|
max_rate=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
trade.adjust_stop_loss(trade.open_rate, 0.05, True)
|
||||||
|
assert trade.stop_loss == 0.95
|
||||||
|
assert trade.stop_loss_pct == -0.05
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
|
# Get percent of profit with a lower rate
|
||||||
|
trade.adjust_stop_loss(0.96, 0.05)
|
||||||
|
assert trade.stop_loss == 0.95
|
||||||
|
assert trade.stop_loss_pct == -0.05
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
|
# Get percent of profit with a custom rate (Higher than open rate)
|
||||||
|
trade.adjust_stop_loss(1.3, -0.1)
|
||||||
|
assert round(trade.stop_loss, 8) == 1.17
|
||||||
|
assert trade.stop_loss_pct == -0.1
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
|
# current rate lower again ... should not change
|
||||||
|
trade.adjust_stop_loss(1.2, 0.1)
|
||||||
|
assert round(trade.stop_loss, 8) == 1.17
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
|
# current rate higher... should raise stoploss
|
||||||
|
trade.adjust_stop_loss(1.4, 0.1)
|
||||||
|
assert round(trade.stop_loss, 8) == 1.26
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
|
# Initial is true but stop_loss set - so doesn't do anything
|
||||||
|
trade.adjust_stop_loss(1.7, 0.1, True)
|
||||||
|
assert round(trade.stop_loss, 8) == 1.26
|
||||||
|
assert trade.initial_stop_loss == 0.95
|
||||||
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
assert trade.stop_loss_pct == -0.1
|
||||||
|
|
||||||
|
|
||||||
|
def test_adjust_min_max_rates(fee):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
@ -595,40 +647,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):
|
|||||||
open_rate=1,
|
open_rate=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
trade.adjust_stop_loss(trade.open_rate, 0.05, True)
|
trade.adjust_min_max_rates(trade.open_rate)
|
||||||
assert trade.stop_loss == 0.95
|
|
||||||
assert trade.max_rate == 1
|
assert trade.max_rate == 1
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.min_rate == 1
|
||||||
|
|
||||||
# Get percent of profit with a lowre rate
|
# check min adjusted, max remained
|
||||||
trade.adjust_stop_loss(0.96, 0.05)
|
trade.adjust_min_max_rates(0.96)
|
||||||
assert trade.stop_loss == 0.95
|
|
||||||
assert trade.max_rate == 1
|
assert trade.max_rate == 1
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.min_rate == 0.96
|
||||||
|
|
||||||
# Get percent of profit with a custom rate (Higher than open rate)
|
# check max adjusted, min remains
|
||||||
trade.adjust_stop_loss(1.3, -0.1)
|
trade.adjust_min_max_rates(1.05)
|
||||||
assert round(trade.stop_loss, 8) == 1.17
|
assert trade.max_rate == 1.05
|
||||||
assert trade.max_rate == 1.3
|
assert trade.min_rate == 0.96
|
||||||
assert trade.initial_stop_loss == 0.95
|
|
||||||
|
|
||||||
# current rate lower again ... should not change
|
# current rate "in the middle" - no adjustment
|
||||||
trade.adjust_stop_loss(1.2, 0.1)
|
trade.adjust_min_max_rates(1.03)
|
||||||
assert round(trade.stop_loss, 8) == 1.17
|
assert trade.max_rate == 1.05
|
||||||
assert trade.max_rate == 1.3
|
assert trade.min_rate == 0.96
|
||||||
assert trade.initial_stop_loss == 0.95
|
|
||||||
|
|
||||||
# current rate higher... should raise stoploss
|
|
||||||
trade.adjust_stop_loss(1.4, 0.1)
|
|
||||||
assert round(trade.stop_loss, 8) == 1.26
|
|
||||||
assert trade.max_rate == 1.4
|
|
||||||
assert trade.initial_stop_loss == 0.95
|
|
||||||
|
|
||||||
# Initial is true but stop_loss set - so doesn't do anything
|
|
||||||
trade.adjust_stop_loss(1.7, 0.1, True)
|
|
||||||
assert round(trade.stop_loss, 8) == 1.26
|
|
||||||
assert trade.max_rate == 1.4
|
|
||||||
assert trade.initial_stop_loss == 0.95
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_open(default_conf, fee):
|
def test_get_open(default_conf, fee):
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
flake8==3.7.7
|
flake8==3.7.7
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==2.0.0
|
flake8-tidy-imports==2.0.0
|
||||||
pytest==4.3.1
|
pytest==4.4.0
|
||||||
pytest-mock==1.10.2
|
pytest-mock==1.10.3
|
||||||
pytest-asyncio==0.10.0
|
pytest-asyncio==0.10.0
|
||||||
pytest-cov==2.6.1
|
pytest-cov==2.6.1
|
||||||
coveralls==1.7.0
|
coveralls==1.7.0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
ccxt==1.18.406
|
ccxt==1.18.425
|
||||||
SQLAlchemy==1.3.1
|
SQLAlchemy==1.3.1
|
||||||
python-telegram-bot==11.1.0
|
python-telegram-bot==11.1.0
|
||||||
arrow==0.13.1
|
arrow==0.13.1
|
||||||
|
Loading…
Reference in New Issue
Block a user