Merge branch 'develop' into sandbox2

This commit is contained in:
Matthias 2018-07-30 20:18:48 +02:00 committed by GitHub
commit 5a55cd25ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 657 additions and 539 deletions

View File

@ -70,8 +70,8 @@ Freqtrade provides a Linux/macOS script to install all dependencies and help you
```bash ```bash
git clone git@github.com:freqtrade/freqtrade.git git clone git@github.com:freqtrade/freqtrade.git
git checkout develop
cd freqtrade cd freqtrade
git checkout develop
./setup.sh --install ./setup.sh --install
``` ```

View File

@ -7,6 +7,7 @@
"ticker_interval": "5m", "ticker_interval": "5m",
"trailing_stop": false, "trailing_stop": false,
"trailing_stop_positive": 0.005, "trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,

View File

@ -39,7 +39,6 @@ A strategy file contains all the information needed to build a good strategy:
- Sell strategy rules - Sell strategy rules
- Minimal ROI recommended - Minimal ROI recommended
- Stoploss recommended - Stoploss recommended
- Hyperopt parameter
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`. The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
You can test it with the parameter: `--strategy TestStrategy` You can test it with the parameter: `--strategy TestStrategy`
@ -61,22 +60,22 @@ file as reference.**
### Buy strategy ### Buy strategy
Edit the method `populate_buy_trend()` into your strategy file to Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
update your buy strategy.
Sample from `user_data/strategies/test_strategy.py`: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
( (
(dataframe['adx'] > 30) & (dataframe['adx'] > 30) &
(dataframe['tema'] <= dataframe['blower']) & (dataframe['tema'] <= dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1)) (dataframe['tema'] > dataframe['tema'].shift(1))
), ),
'buy'] = 1 'buy'] = 1
@ -87,38 +86,47 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
### Sell strategy ### Sell strategy
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy. Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
Sample from `user_data/strategies/test_strategy.py`: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
( (
(dataframe['adx'] > 70) & (dataframe['adx'] > 70) &
(dataframe['tema'] > dataframe['blower']) & (dataframe['tema'] > dataframe['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1)) (dataframe['tema'] < dataframe['tema'].shift(1))
), ),
'sell'] = 1 'sell'] = 1
return dataframe return dataframe
``` ```
## Add more Indicator ## Add more Indicators
As you have seen, buy and sell strategies need indicators. You can add As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
more indicators by extending the list contained in
the method `populate_indicators()` from your strategy file. You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer.
Sample: Sample:
```python ```python
def populate_indicators(dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
""" """
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
@ -149,6 +157,11 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
return dataframe return dataframe
``` ```
### Metadata dict
The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
### Want more indicator examples ### Want more indicator examples
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py). Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).

View File

@ -27,6 +27,7 @@ The table below will list all configuration parameters.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached. | `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive.
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.

View File

@ -35,14 +35,17 @@ basically what this means is that your stop loss will be adjusted to be always b
### Custom positive loss ### Custom positive loss
Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your profit surpasses a certain percentage,
the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you have 1.1% profit,
black, it will be changed to be only a 1% stop loss it will be changed to be only a 1% stop loss, which trails the green candles until it goes below them.
This can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. Both values can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true.
``` json ``` json
"trailing_stop_positive": 0.01, "trailing_stop_positive": 0.01,
"trailing_stop_positive_offset": 0.011,
``` ```
The 0.01 would translate to a 1% stop loss, once you hit profit. The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit.
You should also make sure to have this value higher than your minimal ROI, otherwise minimal ROI will apply first and sell your trade.

View File

@ -63,6 +63,7 @@ CONF_SCHEMA = {
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
'trailing_stop': {'type': 'boolean'}, 'trailing_stop': {'type': 'boolean'},
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'unfilledtimeout': { 'unfilledtimeout': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@ -127,6 +128,8 @@ CONF_SCHEMA = {
'sandbox': {'type': 'boolean'}, 'sandbox': {'type': 'boolean'},
'key': {'type': 'string'}, 'key': {'type': 'string'},
'secret': {'type': 'string'}, 'secret': {'type': 'string'},
'password': {'type': 'string'},
'uid': {'type': 'string'},
'pair_whitelist': { 'pair_whitelist': {
'type': 'array', 'type': 'array',
'items': { 'items': {

View File

@ -4,6 +4,7 @@ import logging
from random import randint from random import randint
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from datetime import datetime from datetime import datetime
from math import floor, ceil
import ccxt import ccxt
import arrow import arrow
@ -162,6 +163,28 @@ class Exchange(object):
""" """
return endpoint in self._api.has and self._api.has[endpoint] return endpoint in self._api.has and self._api.has[endpoint]
def symbol_amount_prec(self, pair, amount: float):
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Rounded down
'''
if self._api.markets[pair]['precision']['amount']:
symbol_prec = self._api.markets[pair]['precision']['amount']
big_amount = amount * pow(10, symbol_prec)
amount = floor(big_amount) / pow(10, symbol_prec)
return amount
def symbol_price_prec(self, pair, price: float):
'''
Returns the price buying or selling with to the precision the Exchange accepts
Rounds up
'''
if self._api.markets[pair]['precision']['price']:
symbol_prec = self._api.markets[pair]['precision']['price']
big_price = price * pow(10, symbol_prec)
price = ceil(big_price) / pow(10, symbol_prec)
return price
def buy(self, pair: str, rate: float, amount: float) -> Dict: def buy(self, pair: str, rate: float, amount: float) -> Dict:
if self._conf['dry_run']: if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}' order_id = f'dry_run_buy_{randint(0, 10**6)}'
@ -179,6 +202,10 @@ class Exchange(object):
return {'id': order_id} return {'id': order_id}
try: try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
return self._api.create_limit_buy_order(pair, amount, rate) return self._api.create_limit_buy_order(pair, amount, rate)
except ccxt.InsufficientFunds as e: except ccxt.InsufficientFunds as e:
raise DependencyException( raise DependencyException(
@ -212,6 +239,10 @@ class Exchange(object):
return {'id': order_id} return {'id': order_id}
try: try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
return self._api.create_limit_sell_order(pair, amount, rate) return self._api.create_limit_sell_order(pair, amount, rate)
except ccxt.InsufficientFunds as e: except ccxt.InsufficientFunds as e:
raise DependencyException( raise DependencyException(

View File

@ -330,7 +330,9 @@ class FreqtradeBot(object):
# Pick pair based on buy signals # Pick pair based on buy signals
for _pair in whitelist: for _pair in whitelist:
(buy, sell) = self.strategy.get_signal(self.exchange, _pair, interval) thistory = self.exchange.get_ticker_history(_pair, interval)
(buy, sell) = self.strategy.get_signal(_pair, interval, thistory)
if buy and not sell: if buy and not sell:
return self.execute_buy(_pair, stake_amount) return self.execute_buy(_pair, stake_amount)
return False return False
@ -495,8 +497,9 @@ class FreqtradeBot(object):
(buy, sell) = (False, False) (buy, sell) = (False, False)
experimental = self.config.get('experimental', {}) experimental = self.config.get('experimental', {})
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'): if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
(buy, sell) = self.strategy.get_signal(self.exchange, ticker = self.exchange.get_ticker_history(trade.pair, self.strategy.ticker_interval)
trade.pair, self.strategy.ticker_interval) (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
ticker)
should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell)
if should_sell.sell_flag: if should_sell.sell_flag:

View File

@ -57,8 +57,8 @@ class Backtesting(object):
self.strategy: IStrategy = StrategyResolver(self.config).strategy self.strategy: IStrategy = StrategyResolver(self.config).strategy
self.ticker_interval = self.strategy.ticker_interval self.ticker_interval = self.strategy.ticker_interval
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
self.populate_buy_trend = self.strategy.populate_buy_trend self.advise_buy = self.strategy.advise_buy
self.populate_sell_trend = self.strategy.populate_sell_trend self.advise_sell = self.strategy.advise_sell
# Reset keys for backtesting # Reset keys for backtesting
self.config['exchange']['key'] = '' self.config['exchange']['key'] = ''
@ -229,8 +229,8 @@ class Backtesting(object):
for pair, pair_data in processed.items(): for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.populate_sell_trend( ticker_data = self.advise_sell(
self.populate_buy_trend(pair_data))[headers].copy() self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle # to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)

View File

@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
return arg_dict return arg_dict
@staticmethod @staticmethod
def populate_indicators(dataframe: DataFrame) -> DataFrame: def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
macd = ta.MACD(dataframe) macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd'] dataframe['macd'] = macd['macd']
@ -228,7 +228,7 @@ class Hyperopt(Backtesting):
""" """
Define the buy strategy parameters to be used by hyperopt Define the buy strategy parameters to be used by hyperopt
""" """
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Buy strategy Hyperopt will build and use Buy strategy Hyperopt will build and use
""" """
@ -270,7 +270,7 @@ class Hyperopt(Backtesting):
self.strategy.minimal_roi = self.generate_roi_table(params) self.strategy.minimal_roi = self.generate_roi_table(params)
if self.has_space('buy'): if self.has_space('buy'):
self.populate_buy_trend = self.buy_strategy_generator(params) self.advise_buy = self.buy_strategy_generator(params)
if self.has_space('stoploss'): if self.has_space('stoploss'):
self.strategy.stoploss = params['stoploss'] self.strategy.stoploss = params['stoploss']
@ -351,7 +351,7 @@ class Hyperopt(Backtesting):
) )
if self.has_space('buy'): if self.has_space('buy'):
self.strategy.populate_indicators = Hyperopt.populate_indicators # type: ignore self.strategy.advise_indicators = Hyperopt.populate_indicators # type: ignore
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE) dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
self.exchange = None # type: ignore self.exchange = None # type: ignore
self.load_previous_results() self.load_previous_results()

View File

@ -28,13 +28,16 @@ class DefaultStrategy(IStrategy):
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage. or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
""" """
# Momentum Indicator # Momentum Indicator
@ -196,10 +199,11 @@ class DefaultStrategy(IStrategy):
return dataframe return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -217,10 +221,11 @@ class DefaultStrategy(IStrategy):
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[

View File

@ -7,13 +7,13 @@ from abc import ABC, abstractmethod
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import Dict, List, NamedTuple, Tuple from typing import Dict, List, NamedTuple, Tuple
import warnings
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade import constants from freqtrade import constants
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -58,34 +58,45 @@ class IStrategy(ABC):
ticker_interval -> str: value of the ticker interval to use for the strategy ticker_interval -> str: value of the ticker interval to use for the strategy
""" """
_populate_fun_len: int = 0
_buy_fun_len: int = 0
_sell_fun_len: int = 0
# associated minimal roi
minimal_roi: Dict minimal_roi: Dict
# associated stoploss
stoploss: float stoploss: float
# associated ticker interval
ticker_interval: str ticker_interval: str
def __init__(self, config: dict) -> None: def __init__(self, config: dict) -> None:
self.config = config self.config = config
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Populate indicators that will be used in the Buy and Sell strategy Populate indicators that will be used in the Buy and Sell strategy
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
@abstractmethod @abstractmethod
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
@abstractmethod @abstractmethod
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with sell column :return: DataFrame with sell column
""" """
@ -95,32 +106,31 @@ class IStrategy(ABC):
""" """
return self.__class__.__name__ return self.__class__.__name__
def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame:
""" """
Parses the given ticker history and returns a populated DataFrame Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data :return DataFrame with ticker data and indicator data
""" """
dataframe = parse_ticker_dataframe(ticker_history) dataframe = parse_ticker_dataframe(ticker_history)
dataframe = self.populate_indicators(dataframe) dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.populate_buy_trend(dataframe) dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.populate_sell_trend(dataframe) dataframe = self.advise_sell(dataframe, metadata)
return dataframe return dataframe
def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]: def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
""" """
Calculates current signal based several technical analysis indicators Calculates current signal based several technical analysis indicators
:param pair: pair in format ANT/BTC :param pair: pair in format ANT/BTC
:param interval: Interval to use (in min) :param interval: Interval to use (in min)
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal :return: (Buy, Sell) A bool-tuple indicating buy/sell signal
""" """
ticker_hist = exchange.get_ticker_history(pair, interval)
if not ticker_hist: if not ticker_hist:
logger.warning('Empty ticker history for pair %s', pair) logger.warning('Empty ticker history for pair %s', pair)
return False, False return False, False
try: try:
dataframe = self.analyze_ticker(ticker_hist) dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
except ValueError as error: except ValueError as error:
logger.warning( logger.warning(
'Unable to analyze ticker for pair %s: %s', 'Unable to analyze ticker for pair %s: %s',
@ -202,6 +212,7 @@ class IStrategy(ABC):
""" """
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
:param current_profit: current profit in percent
""" """
trailing_stop = self.config.get('trailing_stop', False) trailing_stop = self.config.get('trailing_stop', False)
@ -229,12 +240,15 @@ class IStrategy(ABC):
# check if we have a special stop loss for positive condition # check if we have a special stop loss for positive condition
# and if profit is positive # and if profit is positive
stop_loss_value = self.stoploss stop_loss_value = self.stoploss
if 'trailing_stop_positive' in self.config and current_profit > 0: sl_offset = self.config.get('trailing_stop_positive_offset', 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 # Ignore mypy error check in configuration that this is a float
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
logger.debug(f"using positive stop loss mode: {stop_loss_value} " logger.debug(f"using positive stop loss mode: {stop_loss_value} "
f"since we have profit {current_profit}") f"with offset {sl_offset:.4g} "
f"since we have profit {current_profit:.4f}%")
trade.adjust_stop_loss(current_rate, stop_loss_value) trade.adjust_stop_loss(current_rate, stop_loss_value)
@ -261,5 +275,50 @@ class IStrategy(ABC):
""" """
Creates a dataframe and populates indicators for given ticker data Creates a dataframe and populates indicators for given ticker data
""" """
return {pair: self.populate_indicators(parse_ticker_dataframe(pair_data)) return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair})
for pair, pair_data in tickerdata.items()} for pair, pair_data in tickerdata.items()}
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators that will be used in the Buy and Sell strategy
This method should not be overridden.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
if self._populate_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
return self.populate_indicators(dataframe) # type: ignore
else:
return self.populate_indicators(dataframe, metadata)
def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
This method should not be overridden.
:param dataframe: DataFrame
:param pair: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
return self.populate_buy_trend(dataframe) # type: ignore
else:
return self.populate_buy_trend(dataframe, metadata)
def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
This method should not be overridden.
:param dataframe: DataFrame
:param pair: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
if self._sell_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
return self.populate_sell_trend(dataframe) # type: ignore
else:
return self.populate_sell_trend(dataframe, metadata)

View File

@ -92,6 +92,13 @@ class StrategyResolver(object):
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config) strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
if strategy: if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
strategy._populate_fun_len = len(
inspect.getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(
inspect.getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(
inspect.getfullargspec(strategy.populate_sell_trend).args)
return import_strategy(strategy, config=config) return import_strategy(strategy, config=config)
except FileNotFoundError: except FileNotFoundError:
logger.warning('Path "%s" does not exist', path) logger.warning('Path "%s" does not exist', path)

View File

@ -52,6 +52,52 @@ def test_init_exception(default_conf, mocker):
Exchange(default_conf) Exchange(default_conf)
def test_symbol_amount_prec(default_conf, mocker):
'''
Test rounds down to 4 Decimal places
'''
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance'))
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}})
type(api_mock).markets = markets
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
exchange = Exchange(default_conf)
amount = 2.34559
pair = 'ETH/BTC'
amount = exchange.symbol_amount_prec(pair, amount)
assert amount == 2.3455
def test_symbol_price_prec(default_conf, mocker):
'''
Test rounds up to 4 decimal places
'''
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance'))
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}})
type(api_mock).markets = markets
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
exchange = Exchange(default_conf)
price = 2.34559
pair = 'ETH/BTC'
price = exchange.symbol_price_prec(pair, price)
assert price == 2.3456
def test_set_sandbox(default_conf, mocker): def test_set_sandbox(default_conf, mocker):
""" """
Test working scenario Test working scenario
@ -214,7 +260,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
Exchange(default_conf) Exchange(default_conf)
def test_exchangehas(default_conf, mocker): def test_exchange_has(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
assert not exchange.exchange_has('ASDFASDF') assert not exchange.exchange_has('ASDFASDF')
api_mock = MagicMock() api_mock = MagicMock()

View File

@ -1,9 +1,5 @@
# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=missing-docstring, C0103
"""
Unit test file for exchange_helpers.py
"""
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe

View File

@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value):
return signals return signals
def _trend_alternate(dataframe=None): def _trend_alternate(dataframe=None, metadata=None):
signals = dataframe signals = dataframe
low = signals['low'] low = signals['low']
n = len(low) n = len(low)
@ -332,8 +332,8 @@ def test_backtesting_init(mocker, default_conf) -> None:
assert backtesting.config == default_conf assert backtesting.config == default_conf
assert backtesting.ticker_interval == '5m' assert backtesting.ticker_interval == '5m'
assert callable(backtesting.tickerdata_to_dataframe) assert callable(backtesting.tickerdata_to_dataframe)
assert callable(backtesting.populate_buy_trend) assert callable(backtesting.advise_buy)
assert callable(backtesting.populate_sell_trend) assert callable(backtesting.advise_sell)
get_fee.assert_called() get_fee.assert_called()
assert backtesting.fee == 0.5 assert backtesting.fee == 0.5
@ -611,42 +611,42 @@ def test_backtest_ticks(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker) patch_exchange(mocker)
ticks = [1, 5] ticks = [1, 5]
fun = Backtesting(default_conf).populate_buy_trend fun = Backtesting(default_conf).advise_buy
for _ in ticks: for _ in ticks:
backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override backtesting.advise_buy = fun # Override
backtesting.populate_sell_trend = fun # Override backtesting.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(backtest_conf)
assert not results.empty assert not results.empty
def test_backtest_clash_buy_sell(mocker, default_conf): def test_backtest_clash_buy_sell(mocker, default_conf):
# Override the default buy trend function in our default_strategy # Override the default buy trend function in our default_strategy
def fun(dataframe=None): def fun(dataframe=None, pair=None):
buy_value = 1 buy_value = 1
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override backtesting.advise_buy = fun # Override
backtesting.populate_sell_trend = fun # Override backtesting.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(backtest_conf)
assert results.empty assert results.empty
def test_backtest_only_sell(mocker, default_conf): def test_backtest_only_sell(mocker, default_conf):
# Override the default buy trend function in our default_strategy # Override the default buy trend function in our default_strategy
def fun(dataframe=None): def fun(dataframe=None, pair=None):
buy_value = 0 buy_value = 0
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override backtesting.advise_buy = fun # Override
backtesting.populate_sell_trend = fun # Override backtesting.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(backtest_conf)
assert results.empty assert results.empty
@ -655,8 +655,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = _trend_alternate # Override backtesting.advise_buy = _trend_alternate # Override
backtesting.populate_sell_trend = _trend_alternate # Override backtesting.advise_sell = _trend_alternate # Override
results = backtesting.backtest(backtest_conf) results = backtesting.backtest(backtest_conf)
backtesting._store_backtest_result("test_.json", results) backtesting._store_backtest_result("test_.json", results)
assert len(results) == 4 assert len(results) == 4

View File

@ -77,9 +77,6 @@ def test_start(mocker, default_conf, caplog) -> None:
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None: def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
"""
Test Hyperopt.calculate_loss()
"""
hyperopt = _HYPEROPT hyperopt = _HYPEROPT
StrategyResolver({'strategy': 'DefaultStrategy'}) StrategyResolver({'strategy': 'DefaultStrategy'})
@ -91,9 +88,6 @@ def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None: def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
"""
Test Hyperopt.calculate_loss()
"""
hyperopt = _HYPEROPT hyperopt = _HYPEROPT
shorter = hyperopt.calculate_loss(1, 100, 20) shorter = hyperopt.calculate_loss(1, 100, 20)
@ -240,9 +234,6 @@ def test_format_results(init_hyperopt):
def test_has_space(init_hyperopt): def test_has_space(init_hyperopt):
"""
Test Hyperopt.has_space() method
"""
_HYPEROPT.config.update({'spaces': ['buy', 'roi']}) _HYPEROPT.config.update({'spaces': ['buy', 'roi']})
assert _HYPEROPT.has_space('roi') assert _HYPEROPT.has_space('roi')
assert _HYPEROPT.has_space('buy') assert _HYPEROPT.has_space('buy')
@ -253,13 +244,10 @@ def test_has_space(init_hyperopt):
def test_populate_indicators(init_hyperopt) -> None: def test_populate_indicators(init_hyperopt) -> None:
"""
Test Hyperopt.populate_indicators()
"""
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
# Check if some indicators are generated. We will not test all of them # Check if some indicators are generated. We will not test all of them
assert 'adx' in dataframe assert 'adx' in dataframe
@ -268,13 +256,10 @@ def test_populate_indicators(init_hyperopt) -> None:
def test_buy_strategy_generator(init_hyperopt) -> None: def test_buy_strategy_generator(init_hyperopt) -> None:
"""
Test Hyperopt.buy_strategy_generator()
"""
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
populate_buy_trend = _HYPEROPT.buy_strategy_generator( populate_buy_trend = _HYPEROPT.buy_strategy_generator(
{ {
@ -289,16 +274,13 @@ def test_buy_strategy_generator(init_hyperopt) -> None:
'trigger': 'bb_lower' 'trigger': 'bb_lower'
} }
) )
result = populate_buy_trend(dataframe) result = populate_buy_trend(dataframe, {'pair': 'UNITTEST/BTC'})
# Check if some indicators are generated. We will not test all of them # Check if some indicators are generated. We will not test all of them
assert 'buy' in result assert 'buy' in result
assert 1 in result['buy'] assert 1 in result['buy']
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None: def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
"""
Test Hyperopt.generate_optimizer() function
"""
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf.update({'config': 'config.json.example'}) conf.update({'config': 'config.json.example'})
conf.update({'timerange': None}) conf.update({'timerange': None})
@ -335,7 +317,6 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
'roi_p3': 0.1, 'roi_p3': 0.1,
'stoploss': -0.4, 'stoploss': -0.4,
} }
response_expected = { response_expected = {
'loss': 1.9840569076926293, 'loss': 1.9840569076926293,
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' 'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '

View File

@ -53,9 +53,6 @@ def _clean_test_file(file: str) -> None:
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None: def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
"""
Test load_data() with 30 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
_backup_file(file, copy_file=True) _backup_file(file, copy_file=True)
@ -66,9 +63,6 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None: def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
"""
Test load_data() with 5 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
@ -80,11 +74,7 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None: def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
"""
Test load_data() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
_backup_file(file, copy_file=True) _backup_file(file, copy_file=True)
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC']) optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
@ -421,10 +411,6 @@ def test_trim_tickerlist() -> None:
def test_file_dump_json() -> None: def test_file_dump_json() -> None:
"""
Test file_dump_json()
:return: None
"""
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
'test_{id}.json'.format(id=str(uuid.uuid4()))) 'test_{id}.json'.format(id=str(uuid.uuid4())))
data = {'bar': 'foo'} data = {'bar': 'foo'}

View File

@ -1,9 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
"""
Unit test file for rpc/rpc.py
"""
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock, ANY from unittest.mock import MagicMock, ANY
@ -28,9 +25,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:
"""
Test rpc_trade_status() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -72,9 +66,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
"""
Test rpc_status_table() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -106,9 +97,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:
"""
Test rpc_daily_profit() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -158,13 +146,11 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None: limit_buy_order, limit_sell_order, markets, mocker) -> None:
"""
Test rpc_trade_statistics() method
"""
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.fiat_convert.Market', 'freqtrade.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}), ticker=MagicMock(return_value={'price_usd': 15000.0}),
) )
patch_coinmarketcap(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())
mocker.patch.multiple( mocker.patch.multiple(
@ -236,9 +222,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# trade.open_rate (it is set to None) # trade.open_rate (it is set to None)
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
ticker_sell_up, limit_buy_order, limit_sell_order): ticker_sell_up, limit_buy_order, limit_sell_order):
"""
Test rpc_trade_statistics() method
"""
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.fiat_convert.Market', 'freqtrade.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}), ticker=MagicMock(return_value={'price_usd': 15000.0}),
@ -315,6 +298,7 @@ def test_rpc_balance_handle(default_conf, mocker):
'freqtrade.fiat_convert.Market', 'freqtrade.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}), ticker=MagicMock(return_value={'price_usd': 15000.0}),
) )
patch_coinmarketcap(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())
mocker.patch.multiple( mocker.patch.multiple(
@ -342,9 +326,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:
"""
Test rpc_start() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -368,9 +349,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:
"""
Test rpc_stop() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -395,9 +373,6 @@ def test_rpc_stop(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:
"""
Test rpc_forcesell() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -499,9 +474,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:
"""
Test rpc_performance() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -538,9 +510,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:
"""
Test rpc_count() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(

View File

@ -1,6 +1,4 @@
""" # pragma pylint: disable=missing-docstring, C0103
Unit test file for rpc/rpc_manager.py
"""
import logging import logging
from copy import deepcopy from copy import deepcopy
@ -10,14 +8,7 @@ from freqtrade.rpc import RPCMessageType, RPCManager
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
def test_rpc_manager_object() -> None:
""" Test the Arguments object has the mandatory methods """
assert hasattr(RPCManager, 'send_msg')
assert hasattr(RPCManager, 'cleanup')
def test__init__(mocker, default_conf) -> None: def test__init__(mocker, default_conf) -> None:
""" Test __init__() method """
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
@ -26,12 +17,9 @@ def test__init__(mocker, default_conf) -> None:
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
""" Test _init() method with Telegram disabled """
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples) assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples)
@ -39,12 +27,8 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
"""
Test _init() method with Telegram enabled
"""
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert log_has('Enabling rpc.telegram ...', caplog.record_tuples) assert log_has('Enabling rpc.telegram ...', caplog.record_tuples)
@ -54,12 +38,8 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
"""
Test cleanup() method with Telegram disabled
"""
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
@ -72,9 +52,6 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
"""
Test cleanup() method with Telegram enabled
"""
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
@ -92,11 +69,7 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
"""
Test send_msg() method with Telegram disabled
"""
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
@ -112,9 +85,6 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
"""
Test send_msg() method with Telegram disabled
"""
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) telegram_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())
@ -130,13 +100,10 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
def test_init_webhook_disabled(mocker, default_conf, caplog) -> None: def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
""" Test _init() method with Webhook disabled """
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
conf['webhook'] = {'enabled': False} conf['webhook'] = {'enabled': False}
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples) assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples)
@ -144,16 +111,11 @@ def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
def test_init_webhook_enabled(mocker, default_conf, caplog) -> None: def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
"""
Test _init() method with Webhook enabled
"""
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
default_conf['telegram']['enabled'] = False default_conf['telegram']['enabled'] = False
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples) assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
len_modules = len(rpc_manager.registered_modules) assert len(rpc_manager.registered_modules) == 1
assert len_modules == 1
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules] assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]

View File

@ -1,10 +1,7 @@
# pragma pylint: disable=missing-docstring, C0103
# pragma pylint: disable=protected-access, unused-argument, invalid-name # pragma pylint: disable=protected-access, unused-argument, invalid-name
# pragma pylint: disable=too-many-lines, too-many-arguments # pragma pylint: disable=too-many-lines, too-many-arguments
"""
Unit test file for rpc/telegram.py
"""
import re import re
from copy import deepcopy from copy import deepcopy
from datetime import datetime from datetime import datetime
@ -55,9 +52,6 @@ class DummyCls(Telegram):
def test__init__(default_conf, mocker) -> None: def test__init__(default_conf, mocker) -> None:
"""
Test __init__() method
"""
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
@ -67,7 +61,6 @@ def test__init__(default_conf, mocker) -> None:
def test_init(default_conf, mocker, caplog) -> None: def test_init(default_conf, mocker, caplog) -> None:
""" Test _init() method """
start_polling = MagicMock() start_polling = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling)) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
@ -86,9 +79,6 @@ def test_init(default_conf, mocker, caplog) -> None:
def test_cleanup(default_conf, mocker) -> None: def test_cleanup(default_conf, mocker) -> None:
"""
Test cleanup() method
"""
updater_mock = MagicMock() updater_mock = MagicMock()
updater_mock.stop = MagicMock() updater_mock.stop = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock) mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
@ -99,9 +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:
"""
Test authorized_only() method when we are authorized
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
patch_exchange(mocker, None) patch_exchange(mocker, None)
@ -131,9 +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:
"""
Test authorized_only() method when we are unauthorized
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
patch_exchange(mocker, None) patch_exchange(mocker, None)
chat = Chat(0xdeadbeef, 0) chat = Chat(0xdeadbeef, 0)
@ -162,9 +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:
"""
Test authorized_only() method when an exception is thrown
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -195,9 +176,6 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
"""
Test _status() method
"""
update.message.chat.id = 123 update.message.chat.id = 123
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
@ -254,9 +232,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:
"""
Test _status() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -302,9 +277,6 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
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:
"""
Test _status_table() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -357,9 +329,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:
"""
Test _daily() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch( mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
@ -431,9 +400,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:
"""
Test _daily() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -470,9 +436,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:
"""
Test _profit() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) 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.multiple( mocker.patch.multiple(
@ -531,10 +494,6 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
def test_telegram_balance_handle(default_conf, update, mocker) -> None: def test_telegram_balance_handle(default_conf, update, mocker) -> None:
"""
Test _balance() method
"""
mock_balance = { mock_balance = {
'BTC': { 'BTC': {
'total': 12.0, 'total': 12.0,
@ -559,9 +518,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
} }
def mock_ticker(symbol, refresh): def mock_ticker(symbol, refresh):
"""
Mock Bittrex.get_ticker() response
"""
if symbol == 'BTC/USDT': if symbol == 'BTC/USDT':
return { return {
'bid': 10000.00, 'bid': 10000.00,
@ -602,10 +558,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
assert 'BTC: 14.00000000' in result assert 'BTC: 14.00000000' in result
def test_zero_balance_handle(default_conf, update, mocker) -> None: def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
"""
Test _balance() method when the Exchange platform returns nothing
"""
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
msg_mock = MagicMock() msg_mock = MagicMock()
@ -627,9 +580,6 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
def test_start_handle(default_conf, update, mocker) -> None: def test_start_handle(default_conf, update, mocker) -> None:
"""
Test _start() method
"""
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
@ -648,9 +598,6 @@ def test_start_handle(default_conf, update, mocker) -> None:
def test_start_handle_already_running(default_conf, update, mocker) -> None: def test_start_handle_already_running(default_conf, update, mocker) -> None:
"""
Test _start() method
"""
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
@ -670,9 +617,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:
"""
Test _stop() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -693,9 +637,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:
"""
Test _stop() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -716,7 +657,6 @@ def test_stop_handle_already_stopped(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:
""" Test _reload_conf() method """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -738,9 +678,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:
"""
Test _forcesell() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) 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())
@ -790,9 +727,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:
"""
Test _forcesell() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.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())
@ -846,9 +780,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:
"""
Test _forcesell() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.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())
@ -894,9 +825,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:
"""
Test _forcesell() method
"""
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
msg_mock = MagicMock() msg_mock = MagicMock()
@ -937,9 +865,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
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:
"""
Test _performance() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -979,9 +904,6 @@ def test_performance_handle(default_conf, update, ticker, fee,
def test_performance_handle_invalid(default_conf, update, mocker) -> None: def test_performance_handle_invalid(default_conf, update, mocker) -> None:
"""
Test _performance() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -1002,9 +924,6 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None: def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
"""
Test _count() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -1046,9 +965,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
def test_help_handle(default_conf, update, mocker) -> None: def test_help_handle(default_conf, update, mocker) -> None:
"""
Test _help() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -1066,9 +982,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:
"""
Test _version() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -1266,9 +1179,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:
"""
Test send_msg() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -1282,9 +1192,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:
"""
Test send_msg() method
"""
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)

View File

@ -1,9 +1,10 @@
# pragma pylint: disable=missing-docstring, C0103, protected-access
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from requests import RequestException from requests import RequestException
from freqtrade.rpc import RPCMessageType from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.webhook import Webhook from freqtrade.rpc.webhook import Webhook
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
@ -32,23 +33,12 @@ def get_webhook_dict() -> dict:
def test__init__(mocker, default_conf): def test__init__(mocker, default_conf):
"""
Test __init__() method
"""
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
assert webhook._config == default_conf assert webhook._config == default_conf
def test_cleanup(default_conf, mocker) -> None:
"""
Test cleanup() method - not needed for webhook
"""
pass
def test_send_msg(default_conf, mocker): def test_send_msg(default_conf, mocker):
""" Test send_msg for Webhook rpc class"""
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
@ -118,7 +108,6 @@ def test_send_msg(default_conf, mocker):
def test_exception_send_msg(default_conf, mocker, caplog): def test_exception_send_msg(default_conf, mocker, caplog):
"""Test misconfigured notification"""
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
default_conf["webhook"]["webhookbuy"] = None default_conf["webhook"]["webhookbuy"] = None
@ -158,8 +147,6 @@ def test_exception_send_msg(default_conf, mocker, caplog):
def test__send_msg(default_conf, mocker, caplog): def test__send_msg(default_conf, mocker, caplog):
"""Test internal method - calling the actual api"""
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
msg = {'value1': 'DEADBEEF', msg = {'value1': 'DEADBEEF',

View File

@ -0,0 +1,235 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy # noqa
# This class is a sample. Feel free to customize it.
class TestStrategyLegacy(IStrategy):
"""
This is a test strategy using the legacy function headers, which will be
removed in a future update.
Please do not use this as a template, but refer to user_data/strategy/TestStrategy.py
for a uptodate version of this template.
"""
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.10
# Optimal ticker interval for the strategy
ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
"""
# Momentum Indicator
# ------------------------------------
# ADX
dataframe['adx'] = ta.ADX(dataframe)
"""
# Awesome oscillator
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
dataframe['cci'] = ta.CCI(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
# Minus Directional Indicator / Movement
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Plus Directional Indicator / Movement
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# ROC
dataframe['roc'] = ta.ROC(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
rsi = 0.1 * (dataframe['rsi'] - 50)
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# Stoch
stoch = ta.STOCH(dataframe)
dataframe['slowd'] = stoch['slowd']
dataframe['slowk'] = stoch['slowk']
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# Stoch RSI
stoch_rsi = ta.STOCHRSI(dataframe)
dataframe['fastd_rsi'] = stoch_rsi['fastd']
dataframe['fastk_rsi'] = stoch_rsi['fastk']
"""
# Overlap Studies
# ------------------------------------
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
"""
# EMA - Exponential Moving Average
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# SAR Parabol
dataframe['sar'] = ta.SAR(dataframe)
# SMA - Simple Moving Average
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
"""
# TEMA - Triple Exponential Moving Average
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
"""
# Hammer: values [0, 100]
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# Inverted Hammer: values [0, 100]
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# Dragonfly Doji: values [0, 100]
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# Piercing Line: values [0, 100]
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# Morningstar: values [0, 100]
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# Three White Soldiers: values [0, 100]
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
"""
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
"""
# Hanging Man: values [0, 100]
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# Shooting Star: values [0, 100]
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# Gravestone Doji: values [0, 100]
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# Dark Cloud Cover: values [0, 100]
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# Evening Doji Star: values [0, 100]
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# Evening Star: values [0, 100]
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
"""
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
"""
# Three Line Strike: values [0, -100, 100]
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# Spinning Top: values [0, -100, 100]
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# Engulfing: values [0, -100, 100]
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# Harami: values [0, -100, 100]
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# Three Outside Up/Down: values [0, -100, 100]
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# Three Inside Up/Down: values [0, -100, 100]
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
"""
# Chart type
# ------------------------------------
"""
# Heikinashi stategy
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close']
dataframe['ha_high'] = heikinashi['high']
dataframe['ha_low'] = heikinashi['low']
"""
return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 30) &
(dataframe['tema'] <= dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1))
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 70) &
(dataframe['tema'] > dataframe['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
),
'sell'] = 1
return dataframe

View File

@ -25,10 +25,11 @@ def test_default_strategy_structure():
def test_default_strategy(result): def test_default_strategy(result):
strategy = DefaultStrategy({}) strategy = DefaultStrategy({})
metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict assert type(strategy.minimal_roi) is dict
assert type(strategy.stoploss) is float assert type(strategy.stoploss) is float
assert type(strategy.ticker_interval) is str assert type(strategy.ticker_interval) is str
indicators = strategy.populate_indicators(result) indicators = strategy.populate_indicators(result, metadata)
assert type(indicators) is DataFrame assert type(indicators) is DataFrame
assert type(strategy.populate_buy_trend(indicators)) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
assert type(strategy.populate_sell_trend(indicators)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame

View File

@ -1,9 +1,5 @@
# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=missing-docstring, C0103
"""
Unit test file for analyse.py
"""
import logging import logging
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -20,74 +16,62 @@ _STRATEGY = DefaultStrategy(config={})
def test_returns_latest_buy_signal(mocker, default_conf): def test_returns_latest_buy_signal(mocker, default_conf):
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True)
def test_returns_latest_sell_signal(mocker, default_conf): def test_returns_latest_sell_signal(mocker, default_conf):
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False)
def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_empty(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'],
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=None) None)
exchange = get_patched_exchange(mocker, default_conf)
assert (False, False) == _STRATEGY.get_signal(exchange, 'foo', default_conf['ticker_interval'])
assert log_has('Empty ticker history for pair foo', caplog.record_tuples) assert log_has('Empty ticker history for pair foo', caplog.record_tuples)
def test_get_signal_exception_valueerror(default_conf, mocker, caplog): def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
side_effect=ValueError('xyz') side_effect=ValueError('xyz')
) )
assert (False, False) == _STRATEGY.get_signal(exchange, 'foo', default_conf['ticker_interval']) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], 1)
assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples)
def test_get_signal_empty_dataframe(default_conf, mocker, caplog): def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame([]) return_value=DataFrame([])
) )
assert (False, False) == _STRATEGY.get_signal(exchange, 'xyz', default_conf['ticker_interval']) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1)
assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) assert log_has('Empty dataframe for pair xyz', caplog.record_tuples)
def test_get_signal_old_dataframe(default_conf, mocker, caplog): def test_get_signal_old_dataframe(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
# default_conf defines a 5m interval. we check interval * 2 + 5m # default_conf defines a 5m interval. we check interval * 2 + 5m
# this is necessary as the last candle is removed (partial candles) by default # this is necessary as the last candle is removed (partial candles) by default
oldtime = arrow.utcnow().shift(minutes=-16) oldtime = arrow.utcnow().shift(minutes=-16)
@ -96,7 +80,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
_STRATEGY, 'analyze_ticker', _STRATEGY, 'analyze_ticker',
return_value=DataFrame(ticks) return_value=DataFrame(ticks)
) )
assert (False, False) == _STRATEGY.get_signal(exchange, 'xyz', default_conf['ticker_interval']) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1)
assert log_has( assert log_has(
'Outdated history for pair xyz. Last tick is 16 minutes old', 'Outdated history for pair xyz. Last tick is 16 minutes old',
caplog.record_tuples caplog.record_tuples

View File

@ -1,8 +1,10 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103 # pragma pylint: disable=missing-docstring, protected-access, C0103
import logging import logging
import os from os import path
import warnings
import pytest import pytest
from pandas import DataFrame
from freqtrade.strategy import import_strategy from freqtrade.strategy import import_strategy
from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.default_strategy import DefaultStrategy
@ -37,8 +39,8 @@ def test_import_strategy(caplog):
def test_search_strategy(): def test_search_strategy():
default_config = {} default_config = {}
default_location = os.path.join(os.path.dirname( default_location = path.join(path.dirname(
os.path.realpath(__file__)), '..', '..', 'strategy' path.realpath(__file__)), '..', '..', 'strategy'
) )
assert isinstance( assert isinstance(
StrategyResolver._search_strategy( StrategyResolver._search_strategy(
@ -57,13 +59,13 @@ def test_search_strategy():
def test_load_strategy(result): def test_load_strategy(result):
resolver = StrategyResolver({'strategy': 'TestStrategy'}) resolver = StrategyResolver({'strategy': 'TestStrategy'})
assert hasattr(resolver.strategy, 'populate_indicators') metadata = {'pair': 'ETH/BTC'}
assert 'adx' in resolver.strategy.populate_indicators(result) assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata)
def test_load_strategy_invalid_directory(result, caplog): def test_load_strategy_invalid_directory(result, caplog):
resolver = StrategyResolver() resolver = StrategyResolver()
extra_dir = os.path.join('some', 'path') extra_dir = path.join('some', 'path')
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
assert ( assert (
@ -72,8 +74,7 @@ def test_load_strategy_invalid_directory(result, caplog):
'Path "{}" does not exist'.format(extra_dir), 'Path "{}" does not exist'.format(extra_dir),
) in caplog.record_tuples ) in caplog.record_tuples
assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
assert 'adx' in resolver.strategy.populate_indicators(result)
def test_load_not_found_strategy(): def test_load_not_found_strategy():
@ -88,28 +89,23 @@ def test_strategy(result):
config = {'strategy': 'DefaultStrategy'} config = {'strategy': 'DefaultStrategy'}
resolver = StrategyResolver(config) resolver = StrategyResolver(config)
metadata = {'pair': 'ETH/BTC'}
assert hasattr(resolver.strategy, 'minimal_roi')
assert resolver.strategy.minimal_roi[0] == 0.04 assert resolver.strategy.minimal_roi[0] == 0.04
assert config["minimal_roi"]['0'] == 0.04 assert config["minimal_roi"]['0'] == 0.04
assert hasattr(resolver.strategy, 'stoploss')
assert resolver.strategy.stoploss == -0.10 assert resolver.strategy.stoploss == -0.10
assert config['stoploss'] == -0.10 assert config['stoploss'] == -0.10
assert hasattr(resolver.strategy, 'ticker_interval')
assert resolver.strategy.ticker_interval == '5m' assert resolver.strategy.ticker_interval == '5m'
assert config['ticker_interval'] == '5m' assert config['ticker_interval'] == '5m'
assert hasattr(resolver.strategy, 'populate_indicators') df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in resolver.strategy.populate_indicators(result) assert 'adx' in df_indicators
assert hasattr(resolver.strategy, 'populate_buy_trend') dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result))
assert 'buy' in dataframe.columns assert 'buy' in dataframe.columns
assert hasattr(resolver.strategy, 'populate_sell_trend') dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result))
assert 'sell' in dataframe.columns assert 'sell' in dataframe.columns
@ -123,7 +119,6 @@ def test_strategy_override_minimal_roi(caplog):
} }
resolver = StrategyResolver(config) resolver = StrategyResolver(config)
assert hasattr(resolver.strategy, 'minimal_roi')
assert resolver.strategy.minimal_roi[0] == 0.5 assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
@ -139,7 +134,6 @@ def test_strategy_override_stoploss(caplog):
} }
resolver = StrategyResolver(config) resolver = StrategyResolver(config)
assert hasattr(resolver.strategy, 'stoploss')
assert resolver.strategy.stoploss == -0.5 assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
@ -156,9 +150,64 @@ def test_strategy_override_ticker_interval(caplog):
} }
resolver = StrategyResolver(config) resolver = StrategyResolver(config)
assert hasattr(resolver.strategy, 'ticker_interval')
assert resolver.strategy.ticker_interval == 60 assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
'Override strategy \'ticker_interval\' with value in config file: 60.' 'Override strategy \'ticker_interval\' with value in config file: 60.'
) in caplog.record_tuples ) in caplog.record_tuples
def test_deprecate_populate_indicators(result):
default_location = path.join(path.dirname(path.realpath(__file__)))
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
indicators = resolver.strategy.advise_indicators(result, 'ETH/BTC')
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
in str(w[-1].message)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
resolver.strategy.advise_buy(indicators, 'ETH/BTC')
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
in str(w[-1].message)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
resolver.strategy.advise_sell(indicators, 'ETH_BTC')
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
in str(w[-1].message)
def test_call_deprecated_function(result, monkeypatch):
default_location = path.join(path.dirname(path.realpath(__file__)))
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
metadata = {'pair': 'ETH/BTC'}
# Make sure we are using a legacy function
assert resolver.strategy._populate_fun_len == 2
assert resolver.strategy._buy_fun_len == 2
assert resolver.strategy._sell_fun_len == 2
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
assert type(indicator_df) is DataFrame
assert 'adx' in indicator_df.columns
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
assert type(buydf) is DataFrame
assert 'buy' in buydf.columns
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
assert type(selldf) is DataFrame
assert 'sell' in selldf

View File

@ -11,7 +11,6 @@ import freqtrade.tests.conftest as tt # test tools
def whitelist_conf(): def whitelist_conf():
config = tt.default_conf() config = tt.default_conf()
config['stake_currency'] = 'BTC' config['stake_currency'] = 'BTC'
config['exchange']['pair_whitelist'] = [ config['exchange']['pair_whitelist'] = [
'ETH/BTC', 'ETH/BTC',
@ -20,7 +19,6 @@ def whitelist_conf():
'SWT/BTC', 'SWT/BTC',
'BCC/BTC' 'BCC/BTC'
] ]
config['exchange']['pair_blacklist'] = [ config['exchange']['pair_blacklist'] = [
'BLK/BTC' 'BLK/BTC'
] ]

View File

@ -11,23 +11,11 @@ import pytest
from freqtrade.arguments import Arguments, TimeRange from freqtrade.arguments import Arguments, TimeRange
def test_arguments_object() -> None:
"""
Test the Arguments object has the mandatory methods
:return: None
"""
assert hasattr(Arguments, 'get_parsed_arg')
assert hasattr(Arguments, 'parse_args')
assert hasattr(Arguments, 'parse_timerange')
assert hasattr(Arguments, 'scripts_options')
# Parse common command-line-arguments. Used for all tools # Parse common command-line-arguments. Used for all tools
def test_parse_args_none() -> None: def test_parse_args_none() -> None:
arguments = Arguments([], '') arguments = Arguments([], '')
assert isinstance(arguments, Arguments) assert isinstance(arguments, Arguments)
assert isinstance(arguments.parser, argparse.ArgumentParser) assert isinstance(arguments.parser, argparse.ArgumentParser)
assert isinstance(arguments.parser, argparse.ArgumentParser)
def test_parse_args_defaults() -> None: def test_parse_args_defaults() -> None:

View File

@ -1,8 +1,5 @@
# pragma pylint: disable=protected-access, invalid-name # pragma pylint: disable=missing-docstring, protected-access, invalid-name
"""
Unit test file for configuration.py
"""
import json import json
from argparse import Namespace from argparse import Namespace
from copy import deepcopy from copy import deepcopy
@ -19,23 +16,7 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
def test_configuration_object() -> None:
"""
Test the Constants object has the mandatory Constants
"""
assert hasattr(Configuration, 'load_config')
assert hasattr(Configuration, '_load_config_file')
assert hasattr(Configuration, '_validate_config')
assert hasattr(Configuration, '_load_common_config')
assert hasattr(Configuration, '_load_backtesting_config')
assert hasattr(Configuration, '_load_hyperopt_config')
assert hasattr(Configuration, 'get_config')
def test_load_config_invalid_pair(default_conf) -> None: def test_load_config_invalid_pair(default_conf) -> None:
"""
Test the configuration validator with an invalid PAIR format
"""
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'].append('ETH-BTC') conf['exchange']['pair_whitelist'].append('ETH-BTC')
@ -45,9 +26,6 @@ def test_load_config_invalid_pair(default_conf) -> None:
def test_load_config_missing_attributes(default_conf) -> None: def test_load_config_missing_attributes(default_conf) -> None:
"""
Test the configuration validator with a missing attribute
"""
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf.pop('exchange') conf.pop('exchange')
@ -57,9 +35,6 @@ def test_load_config_missing_attributes(default_conf) -> None:
def test_load_config_incorrect_stake_amount(default_conf) -> None: def test_load_config_incorrect_stake_amount(default_conf) -> None:
"""
Test the configuration validator with a missing attribute
"""
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] = 'fake' conf['stake_amount'] = 'fake'
@ -69,9 +44,6 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None:
def test_load_config_file(default_conf, mocker, caplog) -> None: def test_load_config_file(default_conf, mocker, caplog) -> None:
"""
Test Configuration._load_config_file() method
"""
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open( file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
@ -85,9 +57,6 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
"""
Test Configuration._load_config_file() method
"""
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['max_open_trades'] = 0 conf['max_open_trades'] = 0
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open( file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
@ -100,9 +69,6 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
def test_load_config_file_exception(mocker) -> None: def test_load_config_file_exception(mocker) -> None:
"""
Test Configuration._load_config_file() method
"""
mocker.patch( mocker.patch(
'freqtrade.configuration.open', 'freqtrade.configuration.open',
MagicMock(side_effect=FileNotFoundError('File not found')) MagicMock(side_effect=FileNotFoundError('File not found'))
@ -114,9 +80,6 @@ def test_load_config_file_exception(mocker) -> None:
def test_load_config(default_conf, mocker) -> None: def test_load_config(default_conf, mocker) -> None:
"""
Test Configuration.load_config() without any cli params
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
@ -131,13 +94,9 @@ def test_load_config(default_conf, mocker) -> None:
def test_load_config_with_params(default_conf, mocker) -> None: def test_load_config_with_params(default_conf, mocker) -> None:
"""
Test Configuration.load_config() with cli params used
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
arglist = [ arglist = [
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
@ -145,7 +104,6 @@ def test_load_config_with_params(default_conf, mocker) -> None:
'--db-url', 'sqlite:///someurl', '--db-url', 'sqlite:///someurl',
] ]
args = Arguments(arglist, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
validated_conf = configuration.load_config() validated_conf = configuration.load_config()
@ -193,9 +151,6 @@ def test_load_config_with_params(default_conf, mocker) -> None:
def test_load_custom_strategy(default_conf, mocker) -> None: def test_load_custom_strategy(default_conf, mocker) -> None:
"""
Test Configuration.load_config() without any cli params
"""
custom_conf = deepcopy(default_conf) custom_conf = deepcopy(default_conf)
custom_conf.update({ custom_conf.update({
'strategy': 'CustomStrategy', 'strategy': 'CustomStrategy',
@ -214,13 +169,9 @@ def test_load_custom_strategy(default_conf, mocker) -> None:
def test_show_info(default_conf, mocker, caplog) -> None: def test_show_info(default_conf, mocker, caplog) -> None:
"""
Test Configuration.show_info()
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
arglist = [ arglist = [
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
@ -237,19 +188,14 @@ def test_show_info(default_conf, mocker, caplog) -> None:
'(not applicable with Backtesting and Hyperopt)', '(not applicable with Backtesting and Hyperopt)',
caplog.record_tuples caplog.record_tuples
) )
assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples) assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples)
assert log_has('Dry run is enabled', caplog.record_tuples) assert log_has('Dry run is enabled', caplog.record_tuples)
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
"""
Test setup_configuration() function
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
arglist = [ arglist = [
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
@ -287,9 +233,6 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None: def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
"""
Test setup_configuration() function
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
@ -355,19 +298,14 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
"""
Test setup_configuration() function
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
arglist = [ arglist = [
'hyperopt', 'hyperopt',
'--epochs', '10', '--epochs', '10',
'--spaces', 'all', '--spaces', 'all',
] ]
args = Arguments(arglist, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
@ -410,10 +348,6 @@ def test_check_exchange(default_conf) -> None:
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
"""
Test Configuration.load_config() with cli params used
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf))) read_data=json.dumps(default_conf)))
# Prevent setting loggers # Prevent setting loggers
@ -429,9 +363,6 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
def test_set_loggers() -> None: def test_set_loggers() -> None:
"""
Test set_loggers() update the logger level for third-party libraries
"""
# Reset Logging to Debug, otherwise this fails randomly as it's set globally # Reset Logging to Debug, otherwise this fails randomly as it's set globally
logging.getLogger('requests').setLevel(logging.DEBUG) logging.getLogger('requests').setLevel(logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG) logging.getLogger("urllib3").setLevel(logging.DEBUG)

View File

@ -1,25 +0,0 @@
"""
Unit test file for constants.py
"""
from freqtrade import constants
def test_constant_object() -> None:
"""
Test the Constants object has the mandatory Constants
"""
assert hasattr(constants, 'CONF_SCHEMA')
assert hasattr(constants, 'DYNAMIC_WHITELIST')
assert hasattr(constants, 'PROCESS_THROTTLE_SECS')
assert hasattr(constants, 'TICKER_INTERVAL')
assert hasattr(constants, 'HYPEROPT_EPOCH')
assert hasattr(constants, 'RETRY_TIMEOUT')
assert hasattr(constants, 'DEFAULT_STRATEGY')
def test_conf_schema() -> None:
"""
Test the CONF_SCHEMA is from the right type
"""
assert isinstance(constants.CONF_SCHEMA, dict)

View File

@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
assert isinstance(pairs[0], str) assert isinstance(pairs[0], str)
dataframe = ld[pairs[0]] dataframe = ld[pairs[0]]
dataframe = strategy.analyze_ticker(dataframe) dataframe = strategy.analyze_ticker(dataframe, pairs[0])
return dataframe return dataframe

View File

@ -47,6 +47,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
:return: None :return: None
""" """
freqtrade.strategy.get_signal = lambda e, s, t: value freqtrade.strategy.get_signal = lambda e, s, t: value
freqtrade.exchange.get_ticker_history = lambda p, i: None
def patch_RPCManager(mocker) -> MagicMock: def patch_RPCManager(mocker) -> MagicMock:
@ -61,22 +62,6 @@ def patch_RPCManager(mocker) -> MagicMock:
# Unit tests # Unit tests
def test_freqtradebot_object() -> None:
"""
Test the FreqtradeBot object has the mandatory public methods
"""
assert hasattr(FreqtradeBot, 'worker')
assert hasattr(FreqtradeBot, 'cleanup')
assert hasattr(FreqtradeBot, 'create_trade')
assert hasattr(FreqtradeBot, 'get_target_bid')
assert hasattr(FreqtradeBot, 'process_maybe_execute_buy')
assert hasattr(FreqtradeBot, 'process_maybe_execute_sell')
assert hasattr(FreqtradeBot, 'handle_trade')
assert hasattr(FreqtradeBot, 'check_handle_timedout')
assert hasattr(FreqtradeBot, 'handle_timedout_limit_buy')
assert hasattr(FreqtradeBot, 'handle_timedout_limit_sell')
assert hasattr(FreqtradeBot, 'execute_sell')
def test_freqtradebot(mocker, default_conf) -> None: def test_freqtradebot(mocker, default_conf) -> None:
""" """
@ -1813,7 +1798,71 @@ 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 since we have profit 0.26662643', assert log_has(f'using positive stop loss mode: 0.01 with offset 0 '
f'since we have profit 0.2666%',
caplog.record_tuples)
assert log_has(f'adjusted stop loss', caplog.record_tuples)
assert trade.stop_loss == 0.0000138501
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000002,
'ask': buy_price + 0.000002,
'last': buy_price + 0.000002
}))
# Lower price again (but still positive)
assert freqtrade.handle_trade(trade) is True
assert log_has(
f'HIT STOP: current price at {buy_price + 0.000002:.6f}, '
f'stop loss is {trade.stop_loss:.6f}, '
f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples)
def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, mocker) -> None:
"""
Test sell_profit_only feature when enabled and we have a loss
"""
buy_price = limit_buy_order['price']
patch_RPCManager(mocker)
patch_coinmarketcap(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': buy_price - 0.000001,
'ask': buy_price - 0.000001,
'last': buy_price - 0.000001
}),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
conf = deepcopy(default_conf)
conf['trailing_stop'] = True
conf['trailing_stop_positive'] = 0.01
conf['trailing_stop_positive_offset'] = 0.011
freqtrade = FreqtradeBot(conf)
patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False
freqtrade.create_trade()
trade = Trade.query.first()
trade.update(limit_buy_order)
caplog.set_level(logging.DEBUG)
# stop-loss not reached
assert freqtrade.handle_trade(trade) is False
# Raise ticker above buy price
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000003,
'ask': buy_price + 0.000003,
'last': buy_price + 0.000003
}))
# 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%',
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

View File

@ -1,3 +1,5 @@
# pragma pylint: disable=missing-docstring
import pandas as pd import pandas as pd
from freqtrade.indicator_helpers import went_down, went_up from freqtrade.indicator_helpers import went_down, went_up

View File

@ -1,6 +1,4 @@
""" # pragma pylint: disable=missing-docstring
Unit test file for main.py
"""
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -33,9 +31,6 @@ def test_parse_args_backtesting(mocker) -> None:
def test_main_start_hyperopt(mocker) -> None: def test_main_start_hyperopt(mocker) -> None:
"""
Test that main() can start hyperopt
"""
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock()) hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
main(['hyperopt']) main(['hyperopt'])
assert hyperopt_mock.call_count == 1 assert hyperopt_mock.call_count == 1
@ -47,10 +42,6 @@ def test_main_start_hyperopt(mocker) -> None:
def test_main_fatal_exception(mocker, default_conf, caplog) -> None: def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
@ -74,10 +65,6 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
@ -101,10 +88,6 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_operational_exception(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
@ -128,10 +111,6 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
def test_main_reload_conf(mocker, default_conf, caplog) -> None: def test_main_reload_conf(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
@ -158,7 +137,6 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
def test_reconfigure(mocker, default_conf) -> None: def test_reconfigure(mocker, default_conf) -> None:
""" Test recreate() function """
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',

View File

@ -1,9 +1,5 @@
# pragma pylint: disable=missing-docstring,C0103 # pragma pylint: disable=missing-docstring,C0103
"""
Unit test file for misc.py
"""
import datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -15,20 +11,12 @@ from freqtrade.strategy.default_strategy import DefaultStrategy
def test_shorten_date() -> None: def test_shorten_date() -> None:
"""
Test shorten_date() function
:return: None
"""
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago' str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago' str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
assert shorten_date(str_data) == str_shorten_data assert shorten_date(str_data) == str_shorten_data
def test_datesarray_to_datetimearray(ticker_history): def test_datesarray_to_datetimearray(ticker_history):
"""
Test datesarray_to_datetimearray() function
:return: None
"""
dataframes = parse_ticker_dataframe(ticker_history) dataframes = parse_ticker_dataframe(ticker_history)
dates = datesarray_to_datetimearray(dataframes['date']) dates = datesarray_to_datetimearray(dataframes['date'])
@ -44,10 +32,6 @@ def test_datesarray_to_datetimearray(ticker_history):
def test_common_datearray(default_conf) -> None: def test_common_datearray(default_conf) -> None:
"""
Test common_datearray()
:return: None
"""
strategy = DefaultStrategy(default_conf) strategy = DefaultStrategy(default_conf)
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
@ -61,10 +45,6 @@ def test_common_datearray(default_conf) -> None:
def test_file_dump_json(mocker) -> None: def test_file_dump_json(mocker) -> None:
"""
Test file_dump_json()
:return: None
"""
file_open = mocker.patch('freqtrade.misc.open', MagicMock()) file_open = mocker.patch('freqtrade.misc.open', MagicMock())
json_dump = mocker.patch('json.dump', MagicMock()) json_dump = mocker.patch('json.dump', MagicMock())
file_dump_json('somefile', [1, 2, 3]) file_dump_json('somefile', [1, 2, 3])
@ -78,10 +58,6 @@ def test_file_dump_json(mocker) -> None:
def test_format_ms_time() -> None: def test_format_ms_time() -> None:
"""
test format_ms_time()
:return: None
"""
# Date 2018-04-10 18:02:01 # Date 2018-04-10 18:02:01
date_in_epoch_ms = 1523383321000 date_in_epoch_ms = 1523383321000
date = format_ms_time(date_in_epoch_ms) date = format_ms_time(date_in_epoch_ms)

View File

@ -1,14 +0,0 @@
"""
Unit test file for constants.py
"""
from freqtrade.state import State
def test_state_object() -> None:
"""
Test the State object has the mandatory states
:return: None
"""
assert hasattr(State, 'RUNNING')
assert hasattr(State, 'STOPPED')

View File

@ -1,4 +1,4 @@
ccxt==1.17.20 ccxt==1.17.49
SQLAlchemy==1.2.10 SQLAlchemy==1.2.10
python-telegram-bot==10.1.0 python-telegram-bot==10.1.0
arrow==0.12.1 arrow==0.12.1
@ -12,7 +12,7 @@ scipy==1.1.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.15.0 numpy==1.15.0
TA-Lib==0.4.17 TA-Lib==0.4.17
pytest==3.6.3 pytest==3.6.4
pytest-mock==1.10.0 pytest-mock==1.10.0
pytest-cov==2.5.1 pytest-cov==2.5.1
tabulate==0.8.2 tabulate==0.8.2

View File

@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
dataframes = strategy.tickerdata_to_dataframe(tickers) dataframes = strategy.tickerdata_to_dataframe(tickers)
dataframe = dataframes[pair] dataframe = dataframes[pair]
dataframe = strategy.populate_buy_trend(dataframe) dataframe = strategy.advise_buy(dataframe, {'pair': pair})
dataframe = strategy.populate_sell_trend(dataframe) dataframe = strategy.advise_sell(dataframe, {'pair': pair})
if len(dataframe.index) > args.plot_limit: if len(dataframe.index) > args.plot_limit:
logger.warning('Ticker contained more than %s candles as defined ' logger.warning('Ticker contained more than %s candles as defined '

View File

@ -18,6 +18,7 @@ class TestStrategy(IStrategy):
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
You can: You can:
:return: a Dataframe with all mandatory indicators for the strategies
- Rename the class name (Do not forget to update class_name) - Rename the class name (Do not forget to update class_name)
- Add any methods you want to build your strategy - Add any methods you want to build your strategy
- Add any lib you need to build your strategy - Add any lib you need to build your strategy
@ -44,13 +45,16 @@ class TestStrategy(IStrategy):
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage. or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
""" """
# Momentum Indicator # Momentum Indicator
@ -211,10 +215,11 @@ class TestStrategy(IStrategy):
return dataframe return dataframe
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -227,10 +232,11 @@ class TestStrategy(IStrategy):
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[