Merge branch 'develop' into sandbox2
This commit is contained in:
commit
5a55cd25ff
@ -70,8 +70,8 @@ Freqtrade provides a Linux/macOS script to install all dependencies and help you
|
||||
|
||||
```bash
|
||||
git clone git@github.com:freqtrade/freqtrade.git
|
||||
git checkout develop
|
||||
cd freqtrade
|
||||
git checkout develop
|
||||
./setup.sh --install
|
||||
```
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
"ticker_interval": "5m",
|
||||
"trailing_stop": false,
|
||||
"trailing_stop_positive": 0.005,
|
||||
"trailing_stop_positive_offset": 0.0051,
|
||||
"minimal_roi": {
|
||||
"40": 0.0,
|
||||
"30": 0.01,
|
||||
|
@ -39,7 +39,6 @@ A strategy file contains all the information needed to build a good strategy:
|
||||
- Sell strategy rules
|
||||
- Minimal ROI recommended
|
||||
- Stoploss recommended
|
||||
- Hyperopt parameter
|
||||
|
||||
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`
|
||||
@ -61,22 +60,22 @@ file as reference.**
|
||||
|
||||
### Buy strategy
|
||||
|
||||
Edit the method `populate_buy_trend()` into your strategy file to
|
||||
update your buy strategy.
|
||||
Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
|
||||
|
||||
Sample from `user_data/strategies/test_strategy.py`:
|
||||
|
||||
```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
|
||||
:param dataframe: DataFrame
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['tema'] <= dataframe['blower']) &
|
||||
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||
),
|
||||
'buy'] = 1
|
||||
@ -87,38 +86,47 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
### 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`:
|
||||
|
||||
```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
|
||||
:param dataframe: DataFrame
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['tema'] > dataframe['blower']) &
|
||||
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
```
|
||||
|
||||
## Add more Indicator
|
||||
## Add more Indicators
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```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
|
||||
|
||||
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['adx'] = ta.ADX(dataframe)
|
||||
@ -149,6 +157,11 @@ def populate_indicators(dataframe: DataFrame) -> 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
|
||||
|
||||
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
|
||||
|
@ -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.
|
||||
| `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_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.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.
|
||||
|
@ -35,14 +35,17 @@ basically what this means is that your stop loss will be adjusted to be always b
|
||||
|
||||
### 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,
|
||||
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
|
||||
black, it will be changed to be only a 1% stop 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 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 have 1.1% profit,
|
||||
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
|
||||
"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.
|
||||
|
@ -63,6 +63,7 @@ CONF_SCHEMA = {
|
||||
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
|
||||
'trailing_stop': {'type': 'boolean'},
|
||||
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||
'unfilledtimeout': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
@ -127,6 +128,8 @@ CONF_SCHEMA = {
|
||||
'sandbox': {'type': 'boolean'},
|
||||
'key': {'type': 'string'},
|
||||
'secret': {'type': 'string'},
|
||||
'password': {'type': 'string'},
|
||||
'uid': {'type': 'string'},
|
||||
'pair_whitelist': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
|
@ -4,6 +4,7 @@ import logging
|
||||
from random import randint
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from math import floor, ceil
|
||||
|
||||
import ccxt
|
||||
import arrow
|
||||
@ -162,6 +163,28 @@ class Exchange(object):
|
||||
"""
|
||||
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:
|
||||
if self._conf['dry_run']:
|
||||
order_id = f'dry_run_buy_{randint(0, 10**6)}'
|
||||
@ -179,6 +202,10 @@ class Exchange(object):
|
||||
return {'id': order_id}
|
||||
|
||||
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)
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise DependencyException(
|
||||
@ -212,6 +239,10 @@ class Exchange(object):
|
||||
return {'id': order_id}
|
||||
|
||||
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)
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise DependencyException(
|
||||
|
@ -330,7 +330,9 @@ class FreqtradeBot(object):
|
||||
|
||||
# Pick pair based on buy signals
|
||||
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:
|
||||
return self.execute_buy(_pair, stake_amount)
|
||||
return False
|
||||
@ -495,8 +497,9 @@ class FreqtradeBot(object):
|
||||
(buy, sell) = (False, False)
|
||||
experimental = self.config.get('experimental', {})
|
||||
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
|
||||
(buy, sell) = self.strategy.get_signal(self.exchange,
|
||||
trade.pair, self.strategy.ticker_interval)
|
||||
ticker = self.exchange.get_ticker_history(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)
|
||||
if should_sell.sell_flag:
|
||||
|
@ -57,8 +57,8 @@ class Backtesting(object):
|
||||
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
||||
self.ticker_interval = self.strategy.ticker_interval
|
||||
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
||||
self.populate_buy_trend = self.strategy.populate_buy_trend
|
||||
self.populate_sell_trend = self.strategy.populate_sell_trend
|
||||
self.advise_buy = self.strategy.advise_buy
|
||||
self.advise_sell = self.strategy.advise_sell
|
||||
|
||||
# Reset keys for backtesting
|
||||
self.config['exchange']['key'] = ''
|
||||
@ -229,8 +229,8 @@ class Backtesting(object):
|
||||
for pair, pair_data in processed.items():
|
||||
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
|
||||
|
||||
ticker_data = self.populate_sell_trend(
|
||||
self.populate_buy_trend(pair_data))[headers].copy()
|
||||
ticker_data = self.advise_sell(
|
||||
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
|
||||
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
|
||||
|
@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
|
||||
return arg_dict
|
||||
|
||||
@staticmethod
|
||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
||||
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
@ -228,7 +228,7 @@ class Hyperopt(Backtesting):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@ -270,7 +270,7 @@ class Hyperopt(Backtesting):
|
||||
self.strategy.minimal_roi = self.generate_roi_table(params)
|
||||
|
||||
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'):
|
||||
self.strategy.stoploss = params['stoploss']
|
||||
@ -351,7 +351,7 @@ class Hyperopt(Backtesting):
|
||||
)
|
||||
|
||||
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)
|
||||
self.exchange = None # type: ignore
|
||||
self.load_previous_results()
|
||||
@ -360,7 +360,7 @@ class Hyperopt(Backtesting):
|
||||
logger.info(f'Found {cpus} CPU cores. Let\'s make them scream!')
|
||||
|
||||
opt = self.get_optimizer(cpus)
|
||||
EVALS = max(self.total_tries//cpus, 1)
|
||||
EVALS = max(self.total_tries // cpus, 1)
|
||||
try:
|
||||
with Parallel(n_jobs=cpus) as parallel:
|
||||
for i in range(EVALS):
|
||||
|
@ -28,13 +28,16 @@ class DefaultStrategy(IStrategy):
|
||||
# Optimal ticker interval for the strategy
|
||||
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
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
# Momentum Indicator
|
||||
@ -196,10 +199,11 @@ class DefaultStrategy(IStrategy):
|
||||
|
||||
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
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
@ -217,10 +221,11 @@ class DefaultStrategy(IStrategy):
|
||||
|
||||
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
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
|
@ -7,13 +7,13 @@ from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Dict, List, NamedTuple, Tuple
|
||||
import warnings
|
||||
|
||||
import arrow
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -58,34 +58,45 @@ class IStrategy(ABC):
|
||||
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
|
||||
|
||||
# associated stoploss
|
||||
stoploss: float
|
||||
|
||||
# associated ticker interval
|
||||
ticker_interval: str
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
self.config = config
|
||||
|
||||
@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
|
||||
: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
|
||||
"""
|
||||
|
||||
@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
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
|
||||
@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
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with sell column
|
||||
"""
|
||||
|
||||
@ -95,32 +106,31 @@ class IStrategy(ABC):
|
||||
"""
|
||||
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
|
||||
add several TA indicators and buy signal to it
|
||||
:return DataFrame with ticker data and indicator data
|
||||
"""
|
||||
dataframe = parse_ticker_dataframe(ticker_history)
|
||||
dataframe = self.populate_indicators(dataframe)
|
||||
dataframe = self.populate_buy_trend(dataframe)
|
||||
dataframe = self.populate_sell_trend(dataframe)
|
||||
dataframe = self.advise_indicators(dataframe, metadata)
|
||||
dataframe = self.advise_buy(dataframe, metadata)
|
||||
dataframe = self.advise_sell(dataframe, metadata)
|
||||
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
|
||||
:param pair: pair in format ANT/BTC
|
||||
:param interval: Interval to use (in min)
|
||||
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
|
||||
"""
|
||||
ticker_hist = exchange.get_ticker_history(pair, interval)
|
||||
if not ticker_hist:
|
||||
logger.warning('Empty ticker history for pair %s', pair)
|
||||
return False, False
|
||||
|
||||
try:
|
||||
dataframe = self.analyze_ticker(ticker_hist)
|
||||
dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
|
||||
except ValueError as error:
|
||||
logger.warning(
|
||||
'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,
|
||||
decides to sell or not
|
||||
:param current_profit: current profit in percent
|
||||
"""
|
||||
|
||||
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
|
||||
# and if profit is positive
|
||||
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
|
||||
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
||||
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)
|
||||
|
||||
@ -261,5 +275,50 @@ class IStrategy(ABC):
|
||||
"""
|
||||
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()}
|
||||
|
||||
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)
|
||||
|
@ -92,6 +92,13 @@ class StrategyResolver(object):
|
||||
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
|
||||
if strategy:
|
||||
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)
|
||||
except FileNotFoundError:
|
||||
logger.warning('Path "%s" does not exist', path)
|
||||
|
@ -52,6 +52,52 @@ def test_init_exception(default_conf, mocker):
|
||||
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):
|
||||
"""
|
||||
Test working scenario
|
||||
@ -214,7 +260,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_exchangehas(default_conf, mocker):
|
||||
def test_exchange_has(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
assert not exchange.exchange_has('ASDFASDF')
|
||||
api_mock = MagicMock()
|
||||
|
@ -1,9 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
"""
|
||||
Unit test file for exchange_helpers.py
|
||||
"""
|
||||
|
||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||
|
||||
|
||||
|
@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value):
|
||||
return signals
|
||||
|
||||
|
||||
def _trend_alternate(dataframe=None):
|
||||
def _trend_alternate(dataframe=None, metadata=None):
|
||||
signals = dataframe
|
||||
low = signals['low']
|
||||
n = len(low)
|
||||
@ -332,8 +332,8 @@ def test_backtesting_init(mocker, default_conf) -> None:
|
||||
assert backtesting.config == default_conf
|
||||
assert backtesting.ticker_interval == '5m'
|
||||
assert callable(backtesting.tickerdata_to_dataframe)
|
||||
assert callable(backtesting.populate_buy_trend)
|
||||
assert callable(backtesting.populate_sell_trend)
|
||||
assert callable(backtesting.advise_buy)
|
||||
assert callable(backtesting.advise_sell)
|
||||
get_fee.assert_called()
|
||||
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)
|
||||
patch_exchange(mocker)
|
||||
ticks = [1, 5]
|
||||
fun = Backtesting(default_conf).populate_buy_trend
|
||||
fun = Backtesting(default_conf).advise_buy
|
||||
for _ in ticks:
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
backtesting.advise_buy = fun # Override
|
||||
backtesting.advise_sell = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
def fun(dataframe=None, pair=None):
|
||||
buy_value = 1
|
||||
sell_value = 1
|
||||
return _trend(dataframe, buy_value, sell_value)
|
||||
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
backtesting.advise_buy = fun # Override
|
||||
backtesting.advise_sell = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert results.empty
|
||||
|
||||
|
||||
def test_backtest_only_sell(mocker, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
def fun(dataframe=None, pair=None):
|
||||
buy_value = 0
|
||||
sell_value = 1
|
||||
return _trend(dataframe, buy_value, sell_value)
|
||||
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
backtesting.advise_buy = fun # Override
|
||||
backtesting.advise_sell = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
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)
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||
backtesting.advise_buy = _trend_alternate # Override
|
||||
backtesting.advise_sell = _trend_alternate # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
backtesting._store_backtest_result("test_.json", results)
|
||||
assert len(results) == 4
|
||||
|
@ -77,9 +77,6 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
hyperopt = _HYPEROPT
|
||||
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:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
shorter = hyperopt.calculate_loss(1, 100, 20)
|
||||
@ -123,7 +117,7 @@ def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
||||
}
|
||||
)
|
||||
out, err = capsys.readouterr()
|
||||
assert ' 1/2: foo. Loss 1.00000'in out
|
||||
assert ' 1/2: foo. Loss 1.00000' in out
|
||||
|
||||
|
||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||
@ -240,9 +234,6 @@ def test_format_results(init_hyperopt):
|
||||
|
||||
|
||||
def test_has_space(init_hyperopt):
|
||||
"""
|
||||
Test Hyperopt.has_space() method
|
||||
"""
|
||||
_HYPEROPT.config.update({'spaces': ['buy', 'roi']})
|
||||
assert _HYPEROPT.has_space('roi')
|
||||
assert _HYPEROPT.has_space('buy')
|
||||
@ -253,13 +244,10 @@ def test_has_space(init_hyperopt):
|
||||
|
||||
|
||||
def test_populate_indicators(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.populate_indicators()
|
||||
"""
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
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
|
||||
assert 'adx' in dataframe
|
||||
@ -268,13 +256,10 @@ def test_populate_indicators(init_hyperopt) -> None:
|
||||
|
||||
|
||||
def test_buy_strategy_generator(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.buy_strategy_generator()
|
||||
"""
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
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(
|
||||
{
|
||||
@ -289,16 +274,13 @@ def test_buy_strategy_generator(init_hyperopt) -> None:
|
||||
'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
|
||||
assert 'buy' in result
|
||||
assert 1 in result['buy']
|
||||
|
||||
|
||||
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
||||
"""
|
||||
Test Hyperopt.generate_optimizer() function
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
conf.update({'config': 'config.json.example'})
|
||||
conf.update({'timerange': None})
|
||||
@ -335,7 +317,6 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
||||
'roi_p3': 0.1,
|
||||
'stoploss': -0.4,
|
||||
}
|
||||
|
||||
response_expected = {
|
||||
'loss': 1.9840569076926293,
|
||||
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
|
||||
|
@ -53,9 +53,6 @@ def _clean_test_file(file: str) -> 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)
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
|
||||
_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:
|
||||
"""
|
||||
Test load_data() with 5 min ticker
|
||||
"""
|
||||
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')
|
||||
@ -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:
|
||||
"""
|
||||
Test load_data() with 1 min ticker
|
||||
"""
|
||||
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')
|
||||
_backup_file(file, copy_file=True)
|
||||
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:
|
||||
"""
|
||||
Test file_dump_json()
|
||||
:return: None
|
||||
"""
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
||||
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
||||
data = {'bar': 'foo'}
|
||||
|
@ -1,9 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||
|
||||
"""
|
||||
Unit test file for rpc/rpc.py
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, ANY
|
||||
|
||||
@ -28,9 +25,6 @@ def prec_satoshi(a, b) -> float:
|
||||
|
||||
# Unit tests
|
||||
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_status() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
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:
|
||||
"""
|
||||
Test rpc_status_table() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
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,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test rpc_daily_profit() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
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,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.fiat_convert.Market',
|
||||
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.telegram.Telegram', MagicMock())
|
||||
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)
|
||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
|
||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.fiat_convert.Market',
|
||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||
@ -315,6 +298,7 @@ def test_rpc_balance_handle(default_conf, mocker):
|
||||
'freqtrade.fiat_convert.Market',
|
||||
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.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
@ -342,9 +326,6 @@ def test_rpc_balance_handle(default_conf, mocker):
|
||||
|
||||
|
||||
def test_rpc_start(mocker, default_conf) -> None:
|
||||
"""
|
||||
Test rpc_start() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
@ -368,9 +349,6 @@ def test_rpc_start(mocker, default_conf) -> None:
|
||||
|
||||
|
||||
def test_rpc_stop(mocker, default_conf) -> None:
|
||||
"""
|
||||
Test rpc_stop() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
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:
|
||||
"""
|
||||
Test rpc_forcesell() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
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,
|
||||
limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test rpc_performance() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
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:
|
||||
"""
|
||||
Test rpc_count() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Unit test file for rpc/rpc_manager.py
|
||||
"""
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
@ -10,14 +8,7 @@ from freqtrade.rpc import RPCMessageType, RPCManager
|
||||
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:
|
||||
""" Test __init__() method """
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
""" Test _init() method with Telegram disabled """
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['telegram']['enabled'] = False
|
||||
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
||||
|
||||
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:
|
||||
"""
|
||||
Test _init() method with Telegram enabled
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
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:
|
||||
"""
|
||||
Test cleanup() method with Telegram disabled
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test cleanup() method with Telegram enabled
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', 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:
|
||||
"""
|
||||
Test send_msg() method with Telegram disabled
|
||||
"""
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test send_msg() method with Telegram disabled
|
||||
"""
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', 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:
|
||||
""" Test _init() method with Webhook disabled """
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['telegram']['enabled'] = False
|
||||
conf['webhook'] = {'enabled': False}
|
||||
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
||||
|
||||
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:
|
||||
"""
|
||||
Test _init() method with Webhook enabled
|
||||
"""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
||||
len_modules = len(rpc_manager.registered_modules)
|
||||
assert len_modules == 1
|
||||
assert len(rpc_manager.registered_modules) == 1
|
||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
|
@ -1,10 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
# pragma pylint: disable=protected-access, unused-argument, invalid-name
|
||||
# pragma pylint: disable=too-many-lines, too-many-arguments
|
||||
|
||||
"""
|
||||
Unit test file for rpc/telegram.py
|
||||
"""
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
@ -55,9 +52,6 @@ class DummyCls(Telegram):
|
||||
|
||||
|
||||
def test__init__(default_conf, mocker) -> None:
|
||||
"""
|
||||
Test __init__() method
|
||||
"""
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', 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:
|
||||
""" Test _init() method """
|
||||
start_polling = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test cleanup() method
|
||||
"""
|
||||
updater_mock = MagicMock()
|
||||
updater_mock.stop = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test authorized_only() method when we are authorized
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
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:
|
||||
"""
|
||||
Test authorized_only() method when we are unauthorized
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
patch_exchange(mocker, None)
|
||||
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:
|
||||
"""
|
||||
Test authorized_only() method when an exception is thrown
|
||||
"""
|
||||
patch_coinmarketcap(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:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
update.message.chat.id = 123
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test _status_table() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch.multiple(
|
||||
'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,
|
||||
limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test _daily() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch(
|
||||
'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:
|
||||
"""
|
||||
Test _daily() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch.multiple(
|
||||
'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,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test _profit() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
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:
|
||||
"""
|
||||
Test _balance() method
|
||||
"""
|
||||
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'total': 12.0,
|
||||
@ -559,9 +518,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
||||
}
|
||||
|
||||
def mock_ticker(symbol, refresh):
|
||||
"""
|
||||
Mock Bittrex.get_ticker() response
|
||||
"""
|
||||
if symbol == 'BTC/USDT':
|
||||
return {
|
||||
'bid': 10000.00,
|
||||
@ -602,10 +558,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
||||
assert 'BTC: 14.00000000' in result
|
||||
|
||||
|
||||
def test_zero_balance_handle(default_conf, update, mocker) -> None:
|
||||
"""
|
||||
Test _balance() method when the Exchange platform returns nothing
|
||||
"""
|
||||
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||
|
||||
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:
|
||||
"""
|
||||
Test _start() method
|
||||
"""
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test _start() method
|
||||
"""
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test _stop() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test _stop() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
""" Test _reload_conf() method """
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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,
|
||||
ticker_sell_up, markets, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
@ -790,9 +727,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
|
||||
|
||||
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
ticker_sell_down, markets, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 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())
|
||||
@ -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:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 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())
|
||||
@ -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:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
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,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test _performance() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test _performance() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test _count() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test _help() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test _version() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
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:
|
||||
"""
|
||||
Test send_msg() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
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:
|
||||
"""
|
||||
Test send_msg() method
|
||||
"""
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
conf = deepcopy(default_conf)
|
||||
|
@ -1,9 +1,10 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, protected-access
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests import RequestException
|
||||
|
||||
|
||||
from freqtrade.rpc import RPCMessageType
|
||||
from freqtrade.rpc.webhook import Webhook
|
||||
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):
|
||||
"""
|
||||
Test __init__() method
|
||||
"""
|
||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, 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):
|
||||
""" Test send_msg for Webhook rpc class"""
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
msg_mock = MagicMock()
|
||||
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):
|
||||
"""Test misconfigured notification"""
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
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):
|
||||
"""Test internal method - calling the actual api"""
|
||||
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
msg = {'value1': 'DEADBEEF',
|
||||
|
235
freqtrade/tests/strategy/legacy_strategy.py
Normal file
235
freqtrade/tests/strategy/legacy_strategy.py
Normal 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
|
@ -25,10 +25,11 @@ def test_default_strategy_structure():
|
||||
def test_default_strategy(result):
|
||||
strategy = DefaultStrategy({})
|
||||
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
assert type(strategy.minimal_roi) is dict
|
||||
assert type(strategy.stoploss) is float
|
||||
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(strategy.populate_buy_trend(indicators)) is DataFrame
|
||||
assert type(strategy.populate_sell_trend(indicators)) is DataFrame
|
||||
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
|
||||
assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame
|
||||
|
@ -1,9 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
"""
|
||||
Unit test file for analyse.py
|
||||
"""
|
||||
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@ -20,74 +16,62 @@ _STRATEGY = DefaultStrategy(config={})
|
||||
|
||||
|
||||
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(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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):
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch.object(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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):
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=None)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
assert (False, False) == _STRATEGY.get_signal(exchange, 'foo', default_conf['ticker_interval'])
|
||||
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'],
|
||||
None)
|
||||
assert log_has('Empty ticker history for pair foo', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
||||
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(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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)
|
||||
|
||||
|
||||
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
||||
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(
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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)
|
||||
|
||||
|
||||
def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
||||
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
|
||||
# this is necessary as the last candle is removed (partial candles) by default
|
||||
oldtime = arrow.utcnow().shift(minutes=-16)
|
||||
@ -96,7 +80,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
||||
_STRATEGY, 'analyze_ticker',
|
||||
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(
|
||||
'Outdated history for pair xyz. Last tick is 16 minutes old',
|
||||
caplog.record_tuples
|
||||
|
@ -1,8 +1,10 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||
import logging
|
||||
import os
|
||||
from os import path
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import import_strategy
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
@ -37,8 +39,8 @@ def test_import_strategy(caplog):
|
||||
|
||||
def test_search_strategy():
|
||||
default_config = {}
|
||||
default_location = os.path.join(os.path.dirname(
|
||||
os.path.realpath(__file__)), '..', '..', 'strategy'
|
||||
default_location = path.join(path.dirname(
|
||||
path.realpath(__file__)), '..', '..', 'strategy'
|
||||
)
|
||||
assert isinstance(
|
||||
StrategyResolver._search_strategy(
|
||||
@ -57,13 +59,13 @@ def test_search_strategy():
|
||||
|
||||
def test_load_strategy(result):
|
||||
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||
|
||||
|
||||
def test_load_strategy_invalid_directory(result, caplog):
|
||||
resolver = StrategyResolver()
|
||||
extra_dir = os.path.join('some', 'path')
|
||||
extra_dir = path.join('some', 'path')
|
||||
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
|
||||
|
||||
assert (
|
||||
@ -72,8 +74,7 @@ def test_load_strategy_invalid_directory(result, caplog):
|
||||
'Path "{}" does not exist'.format(extra_dir),
|
||||
) in caplog.record_tuples
|
||||
|
||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
|
||||
|
||||
def test_load_not_found_strategy():
|
||||
@ -88,28 +89,23 @@ def test_strategy(result):
|
||||
config = {'strategy': 'DefaultStrategy'}
|
||||
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert hasattr(resolver.strategy, 'minimal_roi')
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
assert resolver.strategy.minimal_roi[0] == 0.04
|
||||
assert config["minimal_roi"]['0'] == 0.04
|
||||
|
||||
assert hasattr(resolver.strategy, 'stoploss')
|
||||
assert resolver.strategy.stoploss == -0.10
|
||||
assert config['stoploss'] == -0.10
|
||||
|
||||
assert hasattr(resolver.strategy, 'ticker_interval')
|
||||
assert resolver.strategy.ticker_interval == '5m'
|
||||
assert config['ticker_interval'] == '5m'
|
||||
|
||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||
df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||
assert 'adx' in df_indicators
|
||||
|
||||
assert hasattr(resolver.strategy, 'populate_buy_trend')
|
||||
dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result))
|
||||
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
|
||||
assert 'buy' in dataframe.columns
|
||||
|
||||
assert hasattr(resolver.strategy, 'populate_sell_trend')
|
||||
dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result))
|
||||
dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
|
||||
assert 'sell' in dataframe.columns
|
||||
|
||||
|
||||
@ -123,7 +119,6 @@ def test_strategy_override_minimal_roi(caplog):
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert hasattr(resolver.strategy, 'minimal_roi')
|
||||
assert resolver.strategy.minimal_roi[0] == 0.5
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
@ -139,7 +134,6 @@ def test_strategy_override_stoploss(caplog):
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert hasattr(resolver.strategy, 'stoploss')
|
||||
assert resolver.strategy.stoploss == -0.5
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
@ -156,9 +150,64 @@ def test_strategy_override_ticker_interval(caplog):
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert hasattr(resolver.strategy, 'ticker_interval')
|
||||
assert resolver.strategy.ticker_interval == 60
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
||||
) 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
|
||||
|
@ -11,7 +11,6 @@ import freqtrade.tests.conftest as tt # test tools
|
||||
|
||||
def whitelist_conf():
|
||||
config = tt.default_conf()
|
||||
|
||||
config['stake_currency'] = 'BTC'
|
||||
config['exchange']['pair_whitelist'] = [
|
||||
'ETH/BTC',
|
||||
@ -20,7 +19,6 @@ def whitelist_conf():
|
||||
'SWT/BTC',
|
||||
'BCC/BTC'
|
||||
]
|
||||
|
||||
config['exchange']['pair_blacklist'] = [
|
||||
'BLK/BTC'
|
||||
]
|
||||
|
@ -11,23 +11,11 @@ import pytest
|
||||
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
|
||||
def test_parse_args_none() -> None:
|
||||
arguments = Arguments([], '')
|
||||
assert isinstance(arguments, Arguments)
|
||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
||||
|
||||
|
||||
def test_parse_args_defaults() -> None:
|
||||
|
@ -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
|
||||
from argparse import Namespace
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Test the configuration validator with an invalid PAIR format
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test the configuration validator with a missing attribute
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test the configuration validator with a missing attribute
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
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:
|
||||
"""
|
||||
Test Configuration._load_config_file() method
|
||||
"""
|
||||
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
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:
|
||||
"""
|
||||
Test Configuration._load_config_file() method
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
conf['max_open_trades'] = 0
|
||||
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:
|
||||
"""
|
||||
Test Configuration._load_config_file() method
|
||||
"""
|
||||
mocker.patch(
|
||||
'freqtrade.configuration.open',
|
||||
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:
|
||||
"""
|
||||
Test Configuration.load_config() without any cli params
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
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:
|
||||
"""
|
||||
Test Configuration.load_config() with cli params used
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
arglist = [
|
||||
'--dynamic-whitelist', '10',
|
||||
'--strategy', 'TestStrategy',
|
||||
@ -145,7 +104,6 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||
'--db-url', 'sqlite:///someurl',
|
||||
]
|
||||
args = Arguments(arglist, '').get_parsed_arg()
|
||||
|
||||
configuration = Configuration(args)
|
||||
validated_conf = configuration.load_config()
|
||||
|
||||
@ -162,10 +120,10 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||
))
|
||||
|
||||
arglist = [
|
||||
'--dynamic-whitelist', '10',
|
||||
'--strategy', 'TestStrategy',
|
||||
'--strategy-path', '/some/path'
|
||||
]
|
||||
'--dynamic-whitelist', '10',
|
||||
'--strategy', 'TestStrategy',
|
||||
'--strategy-path', '/some/path'
|
||||
]
|
||||
args = Arguments(arglist, '').get_parsed_arg()
|
||||
|
||||
configuration = Configuration(args)
|
||||
@ -193,9 +151,6 @@ def test_load_config_with_params(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.update({
|
||||
'strategy': 'CustomStrategy',
|
||||
@ -214,13 +169,9 @@ def test_load_custom_strategy(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_show_info(default_conf, mocker, caplog) -> None:
|
||||
"""
|
||||
Test Configuration.show_info()
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
arglist = [
|
||||
'--dynamic-whitelist', '10',
|
||||
'--strategy', 'TestStrategy',
|
||||
@ -237,19 +188,14 @@ def test_show_info(default_conf, mocker, caplog) -> None:
|
||||
'(not applicable with Backtesting and Hyperopt)',
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples)
|
||||
assert log_has('Dry run is enabled', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||
"""
|
||||
Test setup_configuration() function
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
arglist = [
|
||||
'--config', 'config.json',
|
||||
'--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:
|
||||
"""
|
||||
Test setup_configuration() function
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
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:
|
||||
"""
|
||||
Test setup_configuration() function
|
||||
"""
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
arglist = [
|
||||
'hyperopt',
|
||||
'--epochs', '10',
|
||||
'--spaces', 'all',
|
||||
]
|
||||
|
||||
args = Arguments(arglist, '').get_parsed_arg()
|
||||
|
||||
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:
|
||||
"""
|
||||
Test Configuration.load_config() with cli params used
|
||||
"""
|
||||
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)))
|
||||
# Prevent setting loggers
|
||||
@ -429,9 +363,6 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> 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
|
||||
logging.getLogger('requests').setLevel(logging.DEBUG)
|
||||
logging.getLogger("urllib3").setLevel(logging.DEBUG)
|
||||
|
@ -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)
|
@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
|
||||
assert isinstance(pairs[0], str)
|
||||
dataframe = ld[pairs[0]]
|
||||
|
||||
dataframe = strategy.analyze_ticker(dataframe)
|
||||
dataframe = strategy.analyze_ticker(dataframe, pairs[0])
|
||||
return dataframe
|
||||
|
||||
|
||||
|
@ -47,6 +47,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
|
||||
:return: None
|
||||
"""
|
||||
freqtrade.strategy.get_signal = lambda e, s, t: value
|
||||
freqtrade.exchange.get_ticker_history = lambda p, i: None
|
||||
|
||||
|
||||
def patch_RPCManager(mocker) -> MagicMock:
|
||||
@ -61,22 +62,6 @@ def patch_RPCManager(mocker) -> MagicMock:
|
||||
|
||||
|
||||
# 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:
|
||||
"""
|
||||
@ -1813,7 +1798,71 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
|
||||
}))
|
||||
# stop-loss not reached, adjusted stoploss
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert log_has(f'using positive stop loss mode: 0.01 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)
|
||||
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||
assert trade.stop_loss == 0.0000138501
|
||||
|
@ -1,3 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from freqtrade.indicator_helpers import went_down, went_up
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Unit test file for main.py
|
||||
"""
|
||||
# pragma pylint: disable=missing-docstring
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
@ -33,9 +31,6 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||
|
||||
|
||||
def test_main_start_hyperopt(mocker) -> None:
|
||||
"""
|
||||
Test that main() can start hyperopt
|
||||
"""
|
||||
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
||||
main(['hyperopt'])
|
||||
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:
|
||||
"""
|
||||
Test main() function
|
||||
In this test we are skipping the while True loop by throwing an exception.
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test main() function
|
||||
In this test we are skipping the while True loop by throwing an exception.
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test main() function
|
||||
In this test we are skipping the while True loop by throwing an exception.
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'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:
|
||||
"""
|
||||
Test main() function
|
||||
In this test we are skipping the while True loop by throwing an exception.
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
@ -158,7 +137,6 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
|
||||
|
||||
|
||||
def test_reconfigure(mocker, default_conf) -> None:
|
||||
""" Test recreate() function """
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
|
@ -1,9 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103
|
||||
|
||||
"""
|
||||
Unit test file for misc.py
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@ -15,20 +11,12 @@ from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
|
||||
|
||||
def test_shorten_date() -> None:
|
||||
"""
|
||||
Test shorten_date() function
|
||||
:return: None
|
||||
"""
|
||||
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
|
||||
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
|
||||
assert shorten_date(str_data) == str_shorten_data
|
||||
|
||||
|
||||
def test_datesarray_to_datetimearray(ticker_history):
|
||||
"""
|
||||
Test datesarray_to_datetimearray() function
|
||||
:return: None
|
||||
"""
|
||||
dataframes = parse_ticker_dataframe(ticker_history)
|
||||
dates = datesarray_to_datetimearray(dataframes['date'])
|
||||
|
||||
@ -44,10 +32,6 @@ def test_datesarray_to_datetimearray(ticker_history):
|
||||
|
||||
|
||||
def test_common_datearray(default_conf) -> None:
|
||||
"""
|
||||
Test common_datearray()
|
||||
:return: None
|
||||
"""
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
@ -61,10 +45,6 @@ def test_common_datearray(default_conf) -> None:
|
||||
|
||||
|
||||
def test_file_dump_json(mocker) -> None:
|
||||
"""
|
||||
Test file_dump_json()
|
||||
:return: None
|
||||
"""
|
||||
file_open = mocker.patch('freqtrade.misc.open', MagicMock())
|
||||
json_dump = mocker.patch('json.dump', MagicMock())
|
||||
file_dump_json('somefile', [1, 2, 3])
|
||||
@ -78,10 +58,6 @@ def test_file_dump_json(mocker) -> None:
|
||||
|
||||
|
||||
def test_format_ms_time() -> None:
|
||||
"""
|
||||
test format_ms_time()
|
||||
:return: None
|
||||
"""
|
||||
# Date 2018-04-10 18:02:01
|
||||
date_in_epoch_ms = 1523383321000
|
||||
date = format_ms_time(date_in_epoch_ms)
|
||||
|
@ -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')
|
@ -1,4 +1,4 @@
|
||||
ccxt==1.17.20
|
||||
ccxt==1.17.49
|
||||
SQLAlchemy==1.2.10
|
||||
python-telegram-bot==10.1.0
|
||||
arrow==0.12.1
|
||||
@ -12,7 +12,7 @@ scipy==1.1.0
|
||||
jsonschema==2.6.0
|
||||
numpy==1.15.0
|
||||
TA-Lib==0.4.17
|
||||
pytest==3.6.3
|
||||
pytest==3.6.4
|
||||
pytest-mock==1.10.0
|
||||
pytest-cov==2.5.1
|
||||
tabulate==0.8.2
|
||||
|
@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
|
||||
dataframes = strategy.tickerdata_to_dataframe(tickers)
|
||||
|
||||
dataframe = dataframes[pair]
|
||||
dataframe = strategy.populate_buy_trend(dataframe)
|
||||
dataframe = strategy.populate_sell_trend(dataframe)
|
||||
dataframe = strategy.advise_buy(dataframe, {'pair': pair})
|
||||
dataframe = strategy.advise_sell(dataframe, {'pair': pair})
|
||||
|
||||
if len(dataframe.index) > args.plot_limit:
|
||||
logger.warning('Ticker contained more than %s candles as defined '
|
||||
|
@ -18,6 +18,7 @@ class TestStrategy(IStrategy):
|
||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
||||
|
||||
You can:
|
||||
:return: a Dataframe with all mandatory indicators for the strategies
|
||||
- Rename the class name (Do not forget to update class_name)
|
||||
- Add any methods you want 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
|
||||
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
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
# Momentum Indicator
|
||||
@ -211,10 +215,11 @@ class TestStrategy(IStrategy):
|
||||
|
||||
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
|
||||
:param dataframe: DataFrame
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
@ -227,10 +232,11 @@ class TestStrategy(IStrategy):
|
||||
|
||||
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
|
||||
:param dataframe: DataFrame
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
|
Loading…
Reference in New Issue
Block a user