Merge branch 'develop' into main_refactoring

This commit is contained in:
hroff-1902 2019-04-03 00:50:33 +03:00 committed by GitHub
commit 2959156070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 476 additions and 298 deletions

View File

@ -68,9 +68,9 @@ For any other type of installation please refer to [Installation doc](https://ww
### Bot commands
```
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH]
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt} ...
Free, open source crypto trading bot
@ -84,6 +84,7 @@ positional arguments:
optional arguments:
-h, --help show this help message and exit
-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
-c PATH, --config PATH
Specify configuration file (default: None). Multiple
@ -100,6 +101,7 @@ optional arguments:
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
None).
--sd-notify Notify systemd service manager.
```
### Telegram RPC commands

View File

@ -39,12 +39,12 @@
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": "false",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc",
"sell": "gtc"
},
"pairlist": {
"method": "VolumePairList",

View File

@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it.
## Bot commands
```
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH]
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt} ...
Free, open source crypto trading bot
@ -22,6 +22,7 @@ positional arguments:
optional arguments:
-h, --help show this help message and exit
-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
-c PATH, --config PATH
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.
See more details on this technique with examples in the documentation page on
[configuration](bot-configuration.md).
[configuration](configuration.md).
### How to use **--strategy**?

View File

@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**.
| `strategy` | DefaultStrategy | Defines Strategy class to use.
| `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.
| `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.
### Parameters in the strategy

View File

@ -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.
> **Trade ID:** `123`
> **Current Pair:** CVC/BTC
> **Open Since:** `1 days ago`
> **Amount:** `26.64180098`
> **Open Rate:** `0.00007489`
> **Close Rate:** `None`
> **Current Rate:** `0.00007489`
> **Close Profit:** `None`
> **Current Profit:** `12.95%`
> **Open Order:** `None`
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Open Since:** `1 days ago`
> **Amount:** `26.64180098`
> **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489`
> **Current Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)`
### /status table
@ -99,18 +97,18 @@ current max
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
> ∙ `0.00485701 BTC (258.45%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (143.43%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **ROI:** Close trades
> ∙ `0.00485701 BTC (258.45%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (143.43%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
### /forcesell <trade_id>
@ -128,26 +126,26 @@ Note that for this to work, `forcebuy_enable` needs to be set to true.
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
### /balance
Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
### /daily <n>

View File

@ -71,6 +71,13 @@ class Arguments(object):
dest='loglevel',
default=0,
)
self.parser.add_argument(
'--logfile',
help='Log to the file specified',
dest='logfile',
type=str,
metavar='FILE'
)
self.parser.add_argument(
'--version',
action='version',

View File

@ -4,16 +4,18 @@ This module contains the configuration class
import json
import logging
import os
import sys
from argparse import Namespace
from typing import Any, Dict, Optional
from logging.handlers import RotatingFileHandler
from typing import Any, Dict, List, Optional
import ccxt
from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
from freqtrade.state import RunMode
from freqtrade.misc import deep_merge_dicts
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -116,9 +118,22 @@ class Configuration(object):
config.update({'verbosity': self.args.loglevel})
else:
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(
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=log_handlers
)
set_loggers(config['verbosity'])
logger.info('Verbosity set to %s', config['verbosity'])

View File

@ -367,7 +367,6 @@ class FreqtradeBot(object):
stake_amount = order['cost']
amount = order['amount']
buy_limit_filled_price = order['price']
order_id = None
self.rpc.send_msg({
'type': RPCMessageType.BUY_NOTIFICATION,
@ -396,6 +395,10 @@ class FreqtradeBot(object):
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.flush()
@ -426,24 +429,7 @@ class FreqtradeBot(object):
:return: True if executed
"""
try:
# 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 = 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)
self.update_trade_state(trade)
if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
result = self.handle_stoploss_on_exchange(trade)
@ -508,6 +494,28 @@ class FreqtradeBot(object):
f"(from {order_amount} to {real_amount}) from Trades")
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:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
@ -578,43 +586,44 @@ class FreqtradeBot(object):
"""
result = False
try:
# If trade is open and the buy order is fulfilled but there is no stoploss,
# then we add a stoploss on exchange
if not trade.open_order_id and not trade.stoploss_order_id:
if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair)
else:
stoploss = self.strategy.stoploss
# If trade is open and the buy order is fulfilled but there is no stoploss,
# then we add a stoploss on exchange
if not trade.open_order_id and not trade.stoploss_order_id:
if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair)
else:
stoploss = self.strategy.stoploss
stop_price = trade.open_rate * (1 + stoploss)
stop_price = trade.open_rate * (1 + stoploss)
# limit price should be less than stop price.
# 0.99 is arbitrary here.
limit_price = stop_price * 0.99
# limit price should be less than stop price.
# 0.99 is arbitrary here.
limit_price = stop_price * 0.99
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
trade.stoploss_last_update = datetime.now()
# Or the trade open and there is already a stoploss on exchange.
# so we check if it is hit ...
elif trade.stoploss_order_id:
logger.debug('Handling stoploss on exchange %s ...', trade)
order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
if order['status'] == 'closed':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(order)
self.notify_sell(trade)
result = True
elif self.config.get('trailing_stop', False):
# if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new
# value immediately
self.handle_trailing_stoploss_on_exchange(trade, order)
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
trade.stoploss_last_update = datetime.now()
# Or the trade open and there is already a stoploss on exchange.
# so we check if it is hit ...
elif trade.stoploss_order_id:
logger.debug('Handling stoploss on exchange %s ...', trade)
order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
if order['status'] == 'closed':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(order)
self.notify_sell(trade)
result = True
elif self.config.get('trailing_stop', False):
# if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new
# value immediately
self.handle_trailing_stoploss_on_exchange(trade, order)
except DependencyException as exception:
logger.warning('Unable to create stoploss order: %s', exception)
return result
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):

View File

@ -83,7 +83,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}')
# 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}')
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')
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
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_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null')
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
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')
strategy = get_column_def(cols, 'strategy', '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,
open_rate_requested, close_rate, close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
max_rate, sell_reason, strategy,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, strategy,
ticker_interval
)
select id, lower(exchange),
@ -128,9 +132,11 @@ def check_migrate(engine) -> None:
open_rate, {open_rate_requested} open_rate_requested, close_rate,
{close_rate_requested} close_rate_requested, close_profit,
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,
{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
from {table_back_name}
""")
@ -183,14 +189,20 @@ class Trade(_DECL_BASE):
open_order_id = Column(String)
# absolute value of the stop loss
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
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 = Column(String, nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
sell_reason = Column(String, nullable=True)
strategy = Column(String, 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}, '
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):
"""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):
# 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)))
# 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
if not self.stop_loss:
logger.debug("assigning new stop loss")
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.initial_stop_loss = new_loss
self.initial_stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
# evaluate if the stop loss needs to be updated
else:
if new_loss > self.stop_loss: # stop losses only walk up, never down!
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
logger.debug("adjusted stop loss")
else:

View File

@ -110,6 +110,10 @@ class RPC(object):
amount=round(trade.amount, 8),
close_profit=fmt_close_profit,
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(
order['type'], order['side'], order['remaining']
) if order else None,

View File

@ -194,21 +194,34 @@ class Telegram(RPC):
for result in results:
result['date'] = result['date'].humanize()
messages = [
"*Trade ID:* `{trade_id}`\n"
"*Current Pair:* {pair}\n"
"*Open Since:* `{date}`\n"
"*Amount:* `{amount}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Close Rate:* `{close_rate}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Profit:* `{close_profit}`\n"
"*Current Profit:* `{current_profit:.2f}%`\n"
"*Open Order:* `{open_order}`".format(**result)
for result in results
]
messages = []
for r in results:
lines = [
"*Trade ID:* `{trade_id}` `(since {date})`",
"*Current Pair:* {pair}",
"*Amount:* `{amount}`",
"*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`",
"*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
"*Current Profit:* `{current_profit:.2f}%`",
# 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:
self._send_msg(msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)

View File

@ -247,6 +247,9 @@ class IStrategy(ABC):
"""
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.
: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
"""
@ -254,14 +257,16 @@ class IStrategy(ABC):
current_rate = low or 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,
current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss)
force_stoploss=force_stoploss, high=high)
if stoplossflag.sell_flag:
return stoplossflag
# Set current rate to low for backtesting sell
# Set current rate to high for backtesting sell
current_rate = high or rate
current_profit = trade.calc_profit_percent(current_rate)
experimental = self.config.get('experimental', {})
@ -285,8 +290,9 @@ class IStrategy(ABC):
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
current_profit: float, force_stoploss: float) -> SellCheckTuple:
def stop_loss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, high: float = None) -> SellCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not
@ -294,13 +300,33 @@ class IStrategy(ABC):
"""
trailing_stop = self.config.get('trailing_stop', False)
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
else self.stoploss, initial=True)
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
# 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
if ((self.stoploss is not None) and
(trade.stop_loss >= current_rate) and
(not self.order_types.get('stoploss_on_exchange'))):
(trade.stop_loss >= current_rate) and
(not self.order_types.get('stoploss_on_exchange'))):
selltype = SellType.STOP_LOSS
# If Trailing stop (and max-rate did move above open rate)
if trailing_stop and trade.open_rate != trade.max_rate:
@ -315,29 +341,6 @@ class IStrategy(ABC):
logger.debug('Stop loss hit.')
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)
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:

View File

@ -4,7 +4,6 @@ import logging
import re
from datetime import datetime
from functools import reduce
from typing import Dict, Optional
from unittest.mock import MagicMock, PropertyMock
import arrow
@ -13,11 +12,11 @@ from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.edge import Edge, PairInfo
from freqtrade.worker import Worker
from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.resolvers import ExchangeResolver
from freqtrade.worker import Worker
logging.getLogger('').setLevel(logging.INFO)
@ -96,7 +95,6 @@ def patch_freqtradebot(mocker, config) -> None:
:param config: Config to pass to the bot
:return: None
"""
patch_coinmarketcap(mocker, {'price_usd': 12345.0})
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
patch_exchange(mocker, None)
@ -114,7 +112,8 @@ def get_patched_worker(mocker, config) -> Worker:
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
:param mocker: mocker to patch coinmarketcap class

View File

@ -28,6 +28,7 @@ class BTContainer(NamedTuple):
roi: float
trades: List[BTrade]
profit_perc: float
trailing_stop: bool = False
def _get_frame_time_from_offset(offset):

View File

@ -14,10 +14,10 @@ from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataf
from freqtrade.tests.conftest import patch_exchange
# Test 0 Minus 8% Close
# Test 1 Minus 8% Close
# Test with Stop-loss at 1%
# TC1: Stop-Loss Triggered 1% loss
tc0 = BTContainer(data=[
tc1 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[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%
# TC2: Stop-Loss Triggered 3% Loss
tc1 = BTContainer(data=[
tc2 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[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%.
# Entry Criteria Met
# Candle drops 20%
# Candle Data for test 3
# Test with Stop-Loss at 2%
# TC3: Trade-A: 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
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[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
# Test with Stop-loss at 2% ROI 6%
# TC4: Stop-Loss Triggered 2% Loss
tc3 = BTContainer(data=[
tc4 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[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)]
)
# Test 4 / Drops 0.5% Closes +20%
# Test 5 / Drops 0.5% Closes +20%
# Set stop-loss at 1% ROI 3%
# TC5: ROI triggers 3% Gain
tc4 = BTContainer(data=[
tc5 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[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
# Candle Data for test 6
# Set stop-loss at 2% ROI at 5%
# TC6: Stop-Loss triggers 2% Loss
tc5 = BTContainer(data=[
tc6 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[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
# Candle Data for test 7
# Set stop-loss at 2% ROI at 3%
# TC7: ROI Triggers 3% Gain
tc6 = BTContainer(data=[
tc7 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 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)]
)
# 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 = [
tc0,
tc1,
tc2,
tc3,
tc4,
tc5,
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["minimal_roi"] = {"0": data.roi}
default_conf['ticker_interval'] = tests_ticker_interval
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0))
default_conf["ticker_interval"] = tests_ticker_interval
default_conf["trailing_stop"] = data.trailing_stop
mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0))
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
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
caplog.set_level(logging.DEBUG)
pair = 'UNITTEST/BTC'
pair = "UNITTEST/BTC"
# Dummy data as we mock the analyze functions
data_processed = {pair: DataFrame()}
min_date, max_date = get_timeframe({pair: frame})

View File

@ -8,7 +8,7 @@ import pytest
from requests.exceptions import RequestException
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():
@ -40,7 +40,6 @@ def test_pair_convertion_object():
def test_fiat_convert_is_supported(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
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):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker):
def test_fiat_convert_find_price(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
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):
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
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)
def test_fiat_convert_get_price(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=28000.0)
@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker):
def test_fiat_convert_same_currencies(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
def test_fiat_convert_two_FIAT(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
def test_loadcryptomap(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert len(fiat_convert._cryptomap) == 2
@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker):
def test_fiat_convert_without_network(mocker):
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog):
def test_convert_amount(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
fiat_convert = CryptoToFiatConverter()

View File

@ -14,7 +14,7 @@ from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
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
@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float:
# Unit tests
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -60,6 +59,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
'amount': 90.99181074,
'close_profit': None,
'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)'
} == results[0]
@ -80,12 +83,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
'amount': 90.99181074,
'close_profit': None,
'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)'
} == results[0]
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@ -176,7 +181,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -336,7 +340,6 @@ def test_rpc_balance_handle(default_conf, mocker):
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@ -391,7 +393,6 @@ def test_rpc_start(mocker, default_conf) -> None:
def test_rpc_stop(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@ -416,7 +417,6 @@ def test_rpc_stop(mocker, default_conf) -> None:
def test_rpc_stopbuy(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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,
limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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:
default_conf['forcebuy_enable'] = True
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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:
default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped'
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
default_conf['pairlist'] = {'method': 'VolumePairList',
'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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(

View File

@ -20,8 +20,7 @@ from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import (get_patched_freqtradebot,
log_has, patch_coinmarketcap, patch_exchange)
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker, None)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker, None)
chat = Chat(0xdeadbeef, 0)
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
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']['chat_id'] = 123
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -206,6 +201,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
'amount': 90.99181074,
'close_profit': None,
'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)'
}]),
_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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'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
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 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'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,
limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch(
'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:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch.multiple(
'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,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
@ -548,7 +549,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
'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_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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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,
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)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', 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,
ticker_sell_down, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
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:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
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:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
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:
patch_coinmarketcap(mocker, value={'price_usd': 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._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:
patch_coinmarketcap(mocker, value={'price_usd': 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())
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,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
msg_mock = MagicMock()
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:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
msg_mock = MagicMock()
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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
@ -1165,7 +1148,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
def test_help_handle(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'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:
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
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:
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))

View File

@ -5,6 +5,7 @@ import logging
from argparse import Namespace
from copy import deepcopy
from unittest.mock import MagicMock
from pathlib import Path
import pytest
from jsonschema import Draft4Validator, ValidationError, validate
@ -547,6 +548,23 @@ def test_set_loggers() -> None:
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:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.configuration.open', mocker.mock_open(

View File

@ -1062,6 +1062,13 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
assert trade.stoploss_order_id is None
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,
markets, limit_buy_order, limit_sell_order) -> None:
@ -1312,17 +1319,84 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
# test amount modified by fee-logic
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.open_order_id = None
# 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.*')
assert filter(regexp.match, caplog.record_tuples)
def test_process_maybe_execute_sell_exception(mocker, default_conf,
limit_buy_order, caplog) -> None:
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:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
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',
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)
# 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,
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)
patch_get_signal(freqtrade)
freqtrade.strategy.stop_loss_reached = \
lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple(
sell_flag=False, sell_type=SellType.NONE)
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
sell_flag=False, sell_type=SellType.NONE))
freqtrade.create_trade()
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
assert freqtrade.handle_trade(trade) is False
assert log_has(f'using positive stop loss mode: 0.01 with offset 0 '
f'since we have profit 0.2666%',
assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%',
caplog.record_tuples)
assert log_has(f'adjusted stop loss', caplog.record_tuples)
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
assert freqtrade.handle_trade(trade) is False
assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 '
f'since we have profit 0.2666%',
assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%',
caplog.record_tuples)
assert log_has(f'adjusted stop loss', caplog.record_tuples)
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 log_has(f'using positive stop loss mode: 0.05 with offset 0.055 '
f'since we have profit 0.1218%',
assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%',
caplog.record_tuples)
assert log_has(f'adjusted stop loss', caplog.record_tuples)
assert trade.stop_loss == 0.0000117705

View File

@ -510,6 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.pair == "ETC/BTC"
assert trade.exchange == "binance"
assert trade.max_rate == 0.0
assert trade.min_rate is None
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert trade.sell_reason is None
@ -585,7 +586,58 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
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(
pair='ETH/BTC',
stake_amount=0.001,
@ -595,40 +647,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):
open_rate=1,
)
trade.adjust_stop_loss(trade.open_rate, 0.05, True)
assert trade.stop_loss == 0.95
trade.adjust_min_max_rates(trade.open_rate)
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
trade.adjust_stop_loss(0.96, 0.05)
assert trade.stop_loss == 0.95
# check min adjusted, max remained
trade.adjust_min_max_rates(0.96)
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)
trade.adjust_stop_loss(1.3, -0.1)
assert round(trade.stop_loss, 8) == 1.17
assert trade.max_rate == 1.3
assert trade.initial_stop_loss == 0.95
# check max adjusted, min remains
trade.adjust_min_max_rates(1.05)
assert trade.max_rate == 1.05
assert trade.min_rate == 0.96
# 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.max_rate == 1.3
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
# current rate "in the middle" - no adjustment
trade.adjust_min_max_rates(1.03)
assert trade.max_rate == 1.05
assert trade.min_rate == 0.96
def test_get_open(default_conf, fee):

View File

@ -4,8 +4,8 @@
flake8==3.7.7
flake8-type-annotations==0.1.0
flake8-tidy-imports==2.0.0
pytest==4.3.1
pytest-mock==1.10.2
pytest==4.4.0
pytest-mock==1.10.3
pytest-asyncio==0.10.0
pytest-cov==2.6.1
coveralls==1.7.0

View File

@ -1,4 +1,4 @@
ccxt==1.18.406
ccxt==1.18.425
SQLAlchemy==1.3.1
python-telegram-bot==11.1.0
arrow==0.13.1