merged with feat/short

This commit is contained in:
Sam Germain 2021-09-10 23:16:12 -06:00
commit 694460c8e0
33 changed files with 255 additions and 228 deletions

View File

@ -2,14 +2,16 @@ Thank you for sending your pull request. But first, have you included
unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md) unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
## Summary ## Summary
Explain in one sentence the goal of this PR Explain in one sentence the goal of this PR
Solve the issue: #___ Solve the issue: #___
## Quick changelog ## Quick changelog
- <change log #1> - <change log 1>
- <change log #2> - <change log 1>
## What's new? ## What's new?
*Explain in details what this PR solve or improve. You can include visuals.* *Explain in details what this PR solve or improve. You can include visuals.*

View File

@ -13,7 +13,7 @@ RUN mkdir /freqtrade \
&& apt-get update \ && apt-get update \
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \ && apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
&& apt-get clean \ && apt-get clean \
&& useradd -u 1000 -G sudo -U -m ftuser \ && useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
&& chown ftuser:ftuser /freqtrade \ && chown ftuser:ftuser /freqtrade \
# Allow sudoers # Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers && echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers

View File

@ -12,9 +12,12 @@ if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \ && curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \
&& ./configure --prefix=${INSTALL_LOC}/ \ && ./configure --prefix=${INSTALL_LOC}/ \
&& make -j$(nproc) \ && make -j$(nproc) \
&& which sudo && sudo make install || make install \ && which sudo && sudo make install || make install
&& cd .. if [ -x "$(command -v apt-get)" ]; then
echo "Updating library path using ldconfig"
sudo ldconfig
fi
cd .. && rm -rf ./ta-lib/
else else
echo "TA-lib already installed, skipping installation" echo "TA-lib already installed, skipping installation"
fi fi
# && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \

View File

@ -80,7 +80,7 @@ To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_sp
class MyAwesomeStrategy(IStrategy): class MyAwesomeStrategy(IStrategy):
class HyperOpt: class HyperOpt:
# Define a custom stoploss space. # Define a custom stoploss space.
def stoploss_space(self): def stoploss_space():
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')] return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
``` ```

View File

@ -444,8 +444,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
``` ```
!!! Warning !!! Warning
This is ongoing work. For now, it is supported only for binance. This is ongoing work. For now, it is supported only for binance and kucoin.
Please don't change the default value unless you know what you are doing and have researched the impact of using different values. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
### Exchange configuration ### Exchange configuration

View File

@ -3,7 +3,7 @@
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss. The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
!!! Warning !!! Warning
WHen using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data. When using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
!!! Note !!! Note
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file. `Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.

View File

@ -4,6 +4,8 @@ This page combines common gotchas and informations which are exchange-specific a
## Binance ## Binance
Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
!!! Tip "Stoploss on Exchange" !!! Tip "Stoploss on Exchange"
Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
@ -113,8 +115,12 @@ Kucoin requires a passphrase for each api key, you will therefore need to add th
"key": "your_exchange_key", "key": "your_exchange_key",
"secret": "your_exchange_secret", "secret": "your_exchange_secret",
"password": "your_exchange_api_key_password", "password": "your_exchange_api_key_password",
// ...
}
``` ```
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force).
### Kucoin Blacklists ### Kucoin Blacklists
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues. For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues.
@ -158,6 +164,8 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
"order_time_in_force": ["gtc", "fok"], "order_time_in_force": ["gtc", "fok"],
"ohlcv_candle_limit": 200 "ohlcv_candle_limit": 200
} }
//...
}
``` ```
!!! Warning !!! Warning

View File

@ -1,4 +1,4 @@
mkdocs==1.2.2 mkdocs==1.2.2
mkdocs-material==7.2.5 mkdocs-material==7.2.6
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2
pymdown-extensions==8.2 pymdown-extensions==8.2

View File

@ -22,7 +22,7 @@ if __version__ == 'develop':
# subprocess.check_output( # subprocess.check_output(
# ['git', 'log', '--format="%h"', '-n 1'], # ['git', 'log', '--format="%h"', '-n 1'],
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') # stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
except Exception: except Exception: # pragma: no cover
# git not available, ignore # git not available, ignore
try: try:
# Try Fallback to freqtrade_commit file (created by CI while building docker image) # Try Fallback to freqtrade_commit file (created by CI while building docker image)

View File

@ -61,13 +61,13 @@ def ask_user_config() -> Dict[str, Any]:
"type": "text", "type": "text",
"name": "stake_currency", "name": "stake_currency",
"message": "Please insert your stake currency:", "message": "Please insert your stake currency:",
"default": 'BTC', "default": 'USDT',
}, },
{ {
"type": "text", "type": "text",
"name": "stake_amount", "name": "stake_amount",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "0.01", "default": "100",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"' "filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
if val == UNLIMITED_STAKE_AMOUNT if val == UNLIMITED_STAKE_AMOUNT
@ -105,6 +105,8 @@ def ask_user_config() -> Dict[str, Any]:
"bittrex", "bittrex",
"kraken", "kraken",
"ftx", "ftx",
"kucoin",
"gateio",
Separator(), Separator(),
"other", "other",
], ],
@ -128,6 +130,12 @@ def ask_user_config() -> Dict[str, Any]:
"message": "Insert Exchange Secret", "message": "Insert Exchange Secret",
"when": lambda x: not x['dry_run'] "when": lambda x: not x['dry_run']
}, },
{
"type": "password",
"name": "exchange_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x['dry_run'] and x['exchange_name'] == 'kucoin'
},
{ {
"type": "confirm", "type": "confirm",
"name": "telegram", "name": "telegram",

View File

@ -18,6 +18,7 @@ class Binance(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['gtc', 'fok', 'ioc'],
"time_in_force_parameter": "timeInForce",
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",

View File

@ -54,12 +54,16 @@ class Exchange:
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement) # Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
_params: Dict = {} _params: Dict = {}
# Additional headers - added to the ccxt object
_headers: Dict = {}
# Dict to specify which options each exchange implements # Dict to specify which options each exchange implements
# This defines defaults, which can be selectively overridden by subclasses using _ft_has # This defines defaults, which can be selectively overridden by subclasses using _ft_has
# or by specifying them in the configuration. # or by specifying them in the configuration.
_ft_has_default: Dict = { _ft_has_default: Dict = {
"stoploss_on_exchange": False, "stoploss_on_exchange": False,
"order_time_in_force": ["gtc"], "order_time_in_force": ["gtc"],
"time_in_force_parameter": "timeInForce",
"ohlcv_params": {}, "ohlcv_params": {},
"ohlcv_candle_limit": 500, "ohlcv_candle_limit": 500,
"ohlcv_partial_candle": True, "ohlcv_partial_candle": True,
@ -169,7 +173,7 @@ class Exchange:
asyncio.get_event_loop().run_until_complete(self._api_async.close()) asyncio.get_event_loop().run_until_complete(self._api_async.close())
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt, def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
ccxt_kwargs: dict = None) -> ccxt.Exchange: ccxt_kwargs: Dict = {}) -> ccxt.Exchange:
""" """
Initialize ccxt with given config and return valid Initialize ccxt with given config and return valid
ccxt instance. ccxt instance.
@ -188,6 +192,10 @@ class Exchange:
} }
if ccxt_kwargs: if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs) logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
if self._headers:
# Inject static headers after the above output to not confuse users.
ccxt_kwargs = deep_merge_dicts({'headers': self._headers}, ccxt_kwargs)
if ccxt_kwargs:
ex_config.update(ccxt_kwargs) ex_config.update(ccxt_kwargs)
try: try:
@ -716,7 +724,8 @@ class Exchange:
params = self._params.copy() params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market': if time_in_force != 'gtc' and ordertype != 'market':
params.update({'timeInForce': time_in_force}) param = self._ft_has.get('time_in_force_parameter', '')
params.update({param: time_in_force})
try: try:
# Set the precision for amount and price(rate) as accepted by the exchange # Set the precision for amount and price(rate) as accepted by the exchange

View File

@ -21,4 +21,6 @@ class Kucoin(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"l2_limit_range": [20, 100], "l2_limit_range": [20, 100],
"l2_limit_range_required": False, "l2_limit_range_required": False,
"order_time_in_force": ['gtc', 'fok', 'ioc'],
"time_in_force_parameter": "timeInForce",
} }

View File

@ -100,8 +100,8 @@ class FreqtradeBot(LoggingMixin):
initial_state = self.config.get('initial_state') initial_state = self.config.get('initial_state')
self.state = State[initial_state.upper()] if initial_state else State.STOPPED self.state = State[initial_state.upper()] if initial_state else State.STOPPED
# Protect exit-logic from forcesell and vice versa # Protect sell-logic from forcesell and vice versa
self._sell_lock = Lock() self._exit_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
def notify_status(self, msg: str) -> None: def notify_status(self, msg: str) -> None:
@ -168,14 +168,14 @@ class FreqtradeBot(LoggingMixin):
self.strategy.analyze(self.active_pair_whitelist) self.strategy.analyze(self.active_pair_whitelist)
with self._sell_lock: with self._exit_lock:
# Check and handle any timed out open orders # Check and handle any timed out open orders
self.check_handle_timedout() self.check_handle_timedout()
# Protect from collisions with forceexit. # Protect from collisions with forceexit.
# Without this, freqtrade my try to recreate stoploss_on_exchange orders # Without this, freqtrade my try to recreate stoploss_on_exchange orders
# while exiting is in process, since telegram messages arrive in an different thread. # while selling is in process, since telegram messages arrive in an different thread.
with self._sell_lock: with self._exit_lock:
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
# First process current opened trades (positions) # First process current opened trades (positions)
self.exit_positions(trades) self.exit_positions(trades)
@ -298,9 +298,9 @@ class FreqtradeBot(LoggingMixin):
if sell_order: if sell_order:
self.refind_lost_order(trade) self.refind_lost_order(trade)
else: else:
self.reupdate_buy_order_fees(trade) self.reupdate_enter_order_fees(trade)
def reupdate_buy_order_fees(self, trade: Trade): def reupdate_enter_order_fees(self, trade: Trade):
""" """
Get buy order from database, and try to reupdate. Get buy order from database, and try to reupdate.
Handles trades where the initial fee-update did not work. Handles trades where the initial fee-update did not work.
@ -478,21 +478,21 @@ class FreqtradeBot(LoggingMixin):
time_in_force = self.strategy.order_time_in_force['buy'] time_in_force = self.strategy.order_time_in_force['buy']
if price: if price:
buy_limit_requested = price enter_limit_requested = price
else: else:
# Calculate price # Calculate price
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy") proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=proposed_buy_rate)( default_retval=proposed_enter_rate)(
pair=pair, current_time=datetime.now(timezone.utc), pair=pair, current_time=datetime.now(timezone.utc),
proposed_rate=proposed_buy_rate) proposed_rate=proposed_enter_rate)
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate) enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
if not buy_limit_requested: if not enter_limit_requested:
raise PricingError('Could not determine buy price.') raise PricingError('Could not determine buy price.')
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested, min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested,
self.strategy.stoploss) self.strategy.stoploss)
if not self.edge: if not self.edge:
@ -500,7 +500,7 @@ class FreqtradeBot(LoggingMixin):
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=stake_amount)( default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc), pair=pair, current_time=datetime.now(timezone.utc),
current_rate=buy_limit_requested, proposed_stake=stake_amount, current_rate=enter_limit_requested, proposed_stake=stake_amount,
min_stake=min_stake_amount, max_stake=max_stake_amount) min_stake=min_stake_amount, max_stake=max_stake_amount)
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount) stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
@ -510,7 +510,7 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: "
f"{stake_amount} ...") f"{stake_amount} ...")
amount = stake_amount / buy_limit_requested amount = stake_amount / enter_limit_requested
order_type = self.strategy.order_types['buy'] order_type = self.strategy.order_types['buy']
if forcebuy: if forcebuy:
# Forcebuy can define a different ordertype # Forcebuy can define a different ordertype
@ -519,20 +519,20 @@ class FreqtradeBot(LoggingMixin):
# TODO-lev: Will this work for shorting? # TODO-lev: Will this work for shorting?
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)): time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
logger.info(f"User requested abortion of buying {pair}") logger.info(f"User requested abortion of buying {pair}")
return False return False
amount = self.exchange.amount_to_precision(pair, amount) amount = self.exchange.amount_to_precision(pair, amount)
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
amount=amount, rate=buy_limit_requested, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force) time_in_force=time_in_force)
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
order_id = order['id'] order_id = order['id']
order_status = order.get('status', None) order_status = order.get('status', None)
# we assume the order is executed at the price requested # we assume the order is executed at the price requested
buy_limit_filled_price = buy_limit_requested enter_limit_filled_price = enter_limit_requested
amount_requested = amount amount_requested = amount
if order_status == 'expired' or order_status == 'rejected': if order_status == 'expired' or order_status == 'rejected':
@ -555,13 +555,13 @@ class FreqtradeBot(LoggingMixin):
) )
stake_amount = order['cost'] stake_amount = order['cost']
amount = safe_value_fallback(order, 'filled', 'amount') amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
# in case of FOK the order may be filled immediately and fully # in case of FOK the order may be filled immediately and fully
elif order_status == 'closed': elif order_status == 'closed':
stake_amount = order['cost'] stake_amount = order['cost']
amount = safe_value_fallback(order, 'filled', 'amount') amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
@ -573,8 +573,8 @@ class FreqtradeBot(LoggingMixin):
amount_requested=amount_requested, amount_requested=amount_requested,
fee_open=fee, fee_open=fee,
fee_close=fee, fee_close=fee,
open_rate=buy_limit_filled_price, open_rate=enter_limit_filled_price,
open_rate_requested=buy_limit_requested, open_rate_requested=enter_limit_requested,
open_date=datetime.utcnow(), open_date=datetime.utcnow(),
exchange=self.exchange.id, exchange=self.exchange.id,
open_order_id=order_id, open_order_id=order_id,
@ -594,11 +594,11 @@ class FreqtradeBot(LoggingMixin):
# Updating wallets # Updating wallets
self.wallets.update() self.wallets.update()
self._notify_buy(trade, order_type) self._notify_enter(trade, order_type)
return True return True
def _notify_buy(self, trade: Trade, order_type: str) -> None: def _notify_enter(self, trade: Trade, order_type: str) -> None:
""" """
Sends rpc notification when a entry order occurred. Sends rpc notification when a entry order occurred.
""" """
@ -621,7 +621,7 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
""" """
Sends rpc notification when a entry order cancel occurred. Sends rpc notification when a entry order cancel occurred.
""" """
@ -647,7 +647,7 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
def _notify_buy_fill(self, trade: Trade) -> None: def _notify_enter_fill(self, trade: Trade) -> None:
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.BUY_FILL, 'type': RPCMessageType.BUY_FILL,
@ -717,8 +717,8 @@ class FreqtradeBot(LoggingMixin):
) )
logger.debug('checking sell') logger.debug('checking sell')
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
if self._check_and_execute_sell(trade, sell_rate, buy, sell): if self._check_and_execute_exit(trade, exit_rate, buy, sell):
return True return True
logger.debug('Found no sell signal for %s.', trade) logger.debug('Found no sell signal for %s.', trade)
@ -789,7 +789,7 @@ class FreqtradeBot(LoggingMixin):
# Lock pair for one candle to prevent immediate rebuys # Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
reason='Auto lock') reason='Auto lock')
self._notify_sell(trade, "stoploss") self._notify_exit(trade, "stoploss")
return True return True
if trade.open_order_id or not trade.is_open: if trade.open_order_id or not trade.is_open:
@ -858,19 +858,19 @@ class FreqtradeBot(LoggingMixin):
logger.warning(f"Could not create trailing stoploss order " logger.warning(f"Could not create trailing stoploss order "
f"for pair {trade.pair}.") f"for pair {trade.pair}.")
def _check_and_execute_sell(self, trade: Trade, sell_rate: float, def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
buy: bool, sell: bool) -> bool: buy: bool, sell: bool) -> bool:
""" """
Check and execute sell Check and execute exit
""" """
should_sell = self.strategy.should_sell( should_sell = self.strategy.should_sell(
trade, sell_rate, datetime.now(timezone.utc), buy, sell, trade, exit_rate, datetime.now(timezone.utc), buy, sell,
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
) )
if should_sell.sell_flag: if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_trade_exit(trade, sell_rate, should_sell) self.execute_trade_exit(trade, exit_rate, should_sell)
return True return True
return False return False
@ -913,7 +913,7 @@ class FreqtradeBot(LoggingMixin):
default_retval=False)(pair=trade.pair, default_retval=False)(pair=trade.pair,
trade=trade, trade=trade,
order=order))): order=order))):
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT']) self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
fully_cancelled fully_cancelled
@ -922,7 +922,7 @@ class FreqtradeBot(LoggingMixin):
default_retval=False)(pair=trade.pair, default_retval=False)(pair=trade.pair,
trade=trade, trade=trade,
order=order))): order=order))):
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT']) self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
def cancel_all_open_orders(self) -> None: def cancel_all_open_orders(self) -> None:
""" """
@ -938,13 +938,13 @@ class FreqtradeBot(LoggingMixin):
continue continue
if order['side'] == 'buy': if order['side'] == 'buy':
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
elif order['side'] == 'sell': elif order['side'] == 'sell':
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
Trade.commit() Trade.commit()
def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool: def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
""" """
Buy cancel - cancel order Buy cancel - cancel order
:return: True if order was fully cancelled :return: True if order was fully cancelled
@ -1004,13 +1004,11 @@ class FreqtradeBot(LoggingMixin):
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
self.wallets.update() self.wallets.update()
# TODO-lev: Should short and exit_short be an order type? self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'],
reason=reason)
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
reason=reason)
return was_trade_fully_canceled return was_trade_fully_canceled
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str: def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
""" """
exit order cancel - cancel order and update trade exit order cancel - cancel order and update trade
:return: Reason for cancel :return: Reason for cancel
@ -1044,14 +1042,14 @@ class FreqtradeBot(LoggingMixin):
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
self.wallets.update() self.wallets.update()
self._notify_sell_cancel( self._notify_exit_cancel(
trade, trade,
order_type=self.strategy.order_types['sell'], order_type=self.strategy.order_types['sell'],
reason=reason reason=reason
) )
return reason return reason
def _safe_sell_amount(self, pair: str, amount: float) -> float: def _safe_exit_amount(self, pair: str, amount: float) -> float:
""" """
Get sellable amount. Get sellable amount.
Should be trade.amount - but will fall back to the available amount if necessary. Should be trade.amount - but will fall back to the available amount if necessary.
@ -1124,8 +1122,8 @@ class FreqtradeBot(LoggingMixin):
# but we allow this value to be changed) # but we allow this value to be changed)
order_type = self.strategy.order_types.get("forcesell", order_type) order_type = self.strategy.order_types.get("forcesell", order_type)
amount = self._safe_sell_amount(trade.pair, trade.amount) amount = self._safe_exit_amount(trade.pair, trade.amount)
time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit time_in_force = self.strategy.order_time_in_force['sell']
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
@ -1166,11 +1164,11 @@ class FreqtradeBot(LoggingMixin):
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
reason='Auto lock') reason='Auto lock')
self._notify_sell(trade, order_type) self._notify_exit(trade, order_type)
return True return True
def _notify_sell(self, trade: Trade, order_type: str, fill: bool = False) -> None: def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
""" """
Sends rpc notification when a sell occurred. Sends rpc notification when a sell occurred.
""" """
@ -1212,7 +1210,7 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
""" """
Sends rpc notification when a sell cancel occurred. Sends rpc notification when a sell cancel occurred.
""" """
@ -1307,13 +1305,13 @@ class FreqtradeBot(LoggingMixin):
# Updating wallets when order is closed # Updating wallets when order is closed
if not trade.is_open: if not trade.is_open:
if not stoploss_order and not trade.open_order_id: if not stoploss_order and not trade.open_order_id:
self._notify_sell(trade, '', True) self._notify_exit(trade, '', True)
self.protections.stop_per_pair(trade.pair) self.protections.stop_per_pair(trade.pair)
self.protections.global_stop() self.protections.global_stop()
self.wallets.update() self.wallets.update()
elif not trade.open_order_id: elif not trade.open_order_id:
# Buy fill # Buy fill
self._notify_buy_fill(trade) self._notify_enter_fill(trade)
return False return False

View File

@ -87,7 +87,7 @@ def setup_logging(config: Dict[str, Any]) -> None:
# syslog config. The messages should be equal for this. # syslog config. The messages should be equal for this.
handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
logging.root.addHandler(handler_sl) logging.root.addHandler(handler_sl)
elif s[0] == 'journald': elif s[0] == 'journald': # pragma: no cover
try: try:
from systemd.journal import JournaldLogHandler from systemd.journal import JournaldLogHandler
except ImportError: except ImportError:

View File

@ -9,7 +9,7 @@ from typing import Any, List
# check min. python version # check min. python version
if sys.version_info < (3, 7): if sys.version_info < (3, 7): # pragma: no cover
sys.exit("Freqtrade requires Python version >= 3.7") sys.exit("Freqtrade requires Python version >= 3.7")
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
@ -46,7 +46,7 @@ def main(sysargv: List[str] = None) -> None:
"`freqtrade --help` or `freqtrade <command> --help`." "`freqtrade --help` or `freqtrade <command> --help`."
) )
except SystemExit as e: except SystemExit as e: # pragma: no cover
return_code = e return_code = e
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...') logger.info('SIGINT received, aborting ...')
@ -60,5 +60,5 @@ def main(sysargv: List[str] = None) -> None:
sys.exit(return_code) sys.exit(return_code)
if __name__ == '__main__': if __name__ == '__main__': # pragma: no cover
main() main()

View File

@ -17,7 +17,7 @@ def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
if keep_invalid: if keep_invalid:
for pair_wc in wildcardpl: for pair_wc in wildcardpl:
try: try:
comp = re.compile(pair_wc) comp = re.compile(pair_wc, re.IGNORECASE)
result_partial = [ result_partial = [
pair for pair in available_pairs if re.fullmatch(comp, pair) pair for pair in available_pairs if re.fullmatch(comp, pair)
] ]
@ -33,7 +33,7 @@ def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
else: else:
for pair_wc in wildcardpl: for pair_wc in wildcardpl:
try: try:
comp = re.compile(pair_wc) comp = re.compile(pair_wc, re.IGNORECASE)
result += [ result += [
pair for pair in available_pairs if re.fullmatch(comp, pair) pair for pair in available_pairs if re.fullmatch(comp, pair)
] ]

View File

@ -5,6 +5,20 @@ import time
import uvicorn import uvicorn
def asyncio_setup() -> None: # pragma: no cover
# Set eventloop for win32 setups
# Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop
# via policy.
import sys
if sys.version_info >= (3, 8) and sys.platform == "win32":
import asyncio
import selectors
selector = selectors.SelectSelector()
loop = asyncio.SelectorEventLoop(selector)
asyncio.set_event_loop(loop)
class UvicornServer(uvicorn.Server): class UvicornServer(uvicorn.Server):
""" """
Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742 Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742
@ -28,7 +42,7 @@ class UvicornServer(uvicorn.Server):
try: try:
import uvloop # noqa import uvloop # noqa
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
from uvicorn.loops.asyncio import asyncio_setup
asyncio_setup() asyncio_setup()
else: else:
asyncio.set_event_loop(uvloop.new_event_loop()) asyncio.set_event_loop(uvloop.new_event_loop())

View File

@ -404,8 +404,11 @@ class RPC:
# Doing the sum is not right - overall profit needs to be based on initial capital # Doing the sum is not right - overall profit needs to be based on initial capital
profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0 profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0
starting_balance = self._freqtrade.wallets.get_starting_balance() starting_balance = self._freqtrade.wallets.get_starting_balance()
profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance profit_closed_ratio_fromstart = 0
profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance profit_all_ratio_fromstart = 0
if starting_balance:
profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance
profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance
profit_all_fiat = self._fiat_converter.convert_amount( profit_all_fiat = self._fiat_converter.convert_amount(
profit_all_coin_sum, profit_all_coin_sum,
@ -546,12 +549,12 @@ class RPC:
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
if order['side'] == 'buy': if order['side'] == 'buy':
fully_canceled = self._freqtrade.handle_cancel_buy( fully_canceled = self._freqtrade.handle_cancel_enter(
trade, order, CANCEL_REASON['FORCE_SELL']) trade, order, CANCEL_REASON['FORCE_SELL'])
if order['side'] == 'sell': if order['side'] == 'sell':
# Cancel order - so it is placed anew with a fresh price. # Cancel order - so it is placed anew with a fresh price.
self._freqtrade.handle_cancel_sell(trade, order, CANCEL_REASON['FORCE_SELL']) self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
if not fully_canceled: if not fully_canceled:
# Get current rate and execute sell # Get current rate and execute sell
@ -564,7 +567,7 @@ class RPC:
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running') raise RPCException('trader is not running')
with self._freqtrade._sell_lock: with self._freqtrade._exit_lock:
if trade_id == 'all': if trade_id == 'all':
# Execute sell for all open orders # Execute sell for all open orders
for trade in Trade.get_open_trades(): for trade in Trade.get_open_trades():
@ -626,7 +629,7 @@ class RPC:
Handler for delete <id>. Handler for delete <id>.
Delete the given trade and close eventually existing open orders. Delete the given trade and close eventually existing open orders.
""" """
with self._freqtrade._sell_lock: with self._freqtrade._exit_lock:
c_count = 0 c_count = 0
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
if not trade: if not trade:

View File

@ -1,3 +1,10 @@
{%set volume_pairlist = '{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}' %}
{ {
"max_open_trades": {{ max_open_trades }}, "max_open_trades": {{ max_open_trades }},
"stake_currency": "{{ stake_currency }}", "stake_currency": "{{ stake_currency }}",
@ -29,7 +36,7 @@
}, },
{{ exchange | indent(4) }}, {{ exchange | indent(4) }},
"pairlists": [ "pairlists": [
{"method": "StaticPairList"} {{ '{"method": "StaticPairList"}' if exchange_name == 'bittrex' else volume_pairlist }}
], ],
"edge": { "edge": {
"enabled": false, "enabled": false,

View File

@ -8,34 +8,8 @@
"rateLimit": 200 "rateLimit": 200
}, },
"pair_whitelist": [ "pair_whitelist": [
"ALGO/BTC",
"ATOM/BTC",
"BAT/BTC",
"BCH/BTC",
"BRD/BTC",
"EOS/BTC",
"ETH/BTC",
"IOTA/BTC",
"LINK/BTC",
"LTC/BTC",
"NEO/BTC",
"NXS/BTC",
"XMR/BTC",
"XRP/BTC",
"XTZ/BTC"
], ],
"pair_blacklist": [ "pair_blacklist": [
"BNB/BTC", "BNB/.*"
"BNB/BUSD",
"BNB/ETH",
"BNB/EUR",
"BNB/NGN",
"BNB/PAX",
"BNB/RUB",
"BNB/TRY",
"BNB/TUSD",
"BNB/USDC",
"BNB/USDS",
"BNB/USDT"
] ]
} }

View File

@ -15,16 +15,6 @@
"rateLimit": 500 "rateLimit": 500
}, },
"pair_whitelist": [ "pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
"ETC/BTC",
"DASH/BTC",
"ZEC/BTC",
"XLM/BTC",
"XRP/BTC",
"TRX/BTC",
"ADA/BTC",
"XMR/BTC"
], ],
"pair_blacklist": [ "pair_blacklist": [
] ]

View File

@ -7,28 +7,10 @@
"ccxt_async_config": { "ccxt_async_config": {
"enableRateLimit": true, "enableRateLimit": true,
"rateLimit": 1000 "rateLimit": 1000
// Enable the below for downoading data.
//"rateLimit": 3100
}, },
"pair_whitelist": [ "pair_whitelist": [
"ADA/EUR",
"ATOM/EUR",
"BAT/EUR",
"BCH/EUR",
"BTC/EUR",
"DAI/EUR",
"DASH/EUR",
"EOS/EUR",
"ETC/EUR",
"ETH/EUR",
"LINK/EUR",
"LTC/EUR",
"QTUM/EUR",
"REP/EUR",
"WAVES/EUR",
"XLM/EUR",
"XMR/EUR",
"XRP/EUR",
"XTZ/EUR",
"ZEC/EUR"
], ],
"pair_blacklist": [ "pair_blacklist": [

View File

@ -0,0 +1,18 @@
"exchange": {
"name": "{{ exchange_name | lower }}",
"key": "{{ exchange_key }}",
"secret": "{{ exchange_secret }}",
"password": "{{ exchange_key_password }}",
"ccxt_config": {
"enableRateLimit": true
"rateLimit": 200
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 200
},
"pair_whitelist": [
],
"pair_blacklist": [
]
}

View File

@ -3,42 +3,42 @@ site_url: https://www.freqtrade.io/
repo_url: https://github.com/freqtrade/freqtrade repo_url: https://github.com/freqtrade/freqtrade
use_directory_urls: True use_directory_urls: True
nav: nav:
- Home: index.md - Home: index.md
- Quickstart with Docker: docker_quickstart.md - Quickstart with Docker: docker_quickstart.md
- Installation: - Installation:
- Linux/MacOS/Raspberry: installation.md - Linux/MacOS/Raspberry: installation.md
- Windows: windows_installation.md - Windows: windows_installation.md
- Freqtrade Basics: bot-basics.md - Freqtrade Basics: bot-basics.md
- Configuration: configuration.md - Configuration: configuration.md
- Strategy Customization: strategy-customization.md - Strategy Customization: strategy-customization.md
- Plugins: plugins.md - Plugins: plugins.md
- Stoploss: stoploss.md - Stoploss: stoploss.md
- Start the bot: bot-usage.md - Start the bot: bot-usage.md
- Control the bot: - Control the bot:
- Telegram: telegram-usage.md - Telegram: telegram-usage.md
- REST API & FreqUI: rest-api.md - REST API & FreqUI: rest-api.md
- Web Hook: webhook-config.md - Web Hook: webhook-config.md
- Data Downloading: data-download.md - Data Downloading: data-download.md
- Backtesting: backtesting.md - Backtesting: backtesting.md
- Leverage: leverage.md - Hyperopt: hyperopt.md
- Hyperopt: hyperopt.md - Leverage: leverage.md
- Utility Sub-commands: utils.md - Utility Sub-commands: utils.md
- Plotting: plotting.md - Plotting: plotting.md
- Data Analysis: - Exchange-specific Notes: exchanges.md
- Jupyter Notebooks: data-analysis.md - Data Analysis:
- Strategy analysis: strategy_analysis_example.md - Jupyter Notebooks: data-analysis.md
- Exchange-specific Notes: exchanges.md - Strategy analysis: strategy_analysis_example.md
- Advanced Topics: - Advanced Topics:
- Advanced Post-installation Tasks: advanced-setup.md - Advanced Post-installation Tasks: advanced-setup.md
- Edge Positioning: edge.md - Edge Positioning: edge.md
- Advanced Strategy: strategy-advanced.md - Advanced Strategy: strategy-advanced.md
- Advanced Hyperopt: advanced-hyperopt.md - Advanced Hyperopt: advanced-hyperopt.md
- Sandbox Testing: sandbox-testing.md - Sandbox Testing: sandbox-testing.md
- FAQ: faq.md - FAQ: faq.md
- SQL Cheat-sheet: sql_cheatsheet.md - SQL Cheat-sheet: sql_cheatsheet.md
- Updating Freqtrade: updating.md - Updating Freqtrade: updating.md
- Deprecated Features: deprecated.md - Deprecated Features: deprecated.md
- Contributors Guide: developer.md - Contributors Guide: developer.md
theme: theme:
name: material name: material
logo: "images/logo.png" logo: "images/logo.png"

View File

@ -8,7 +8,7 @@ flake8==3.9.2
flake8-type-annotations==0.1.0 flake8-type-annotations==0.1.0
flake8-tidy-imports==4.4.1 flake8-tidy-imports==4.4.1
mypy==0.910 mypy==0.910
pytest==6.2.4 pytest==6.2.5
pytest-asyncio==0.15.1 pytest-asyncio==0.15.1
pytest-cov==2.12.1 pytest-cov==2.12.1
pytest-mock==3.6.1 pytest-mock==3.6.1

View File

@ -1,5 +1,5 @@
# Include all requirements to run the bot. # Include all requirements to run the bot.
-r requirements.txt -r requirements.txt
plotly==5.3.0 plotly==5.3.1

View File

@ -1,7 +1,7 @@
numpy==1.21.2 numpy==1.21.2
pandas==1.3.2 pandas==1.3.2
ccxt==1.55.56 ccxt==1.55.83
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==3.4.8 cryptography==3.4.8
aiohttp==3.7.4.post0 aiohttp==3.7.4.post0

View File

@ -95,19 +95,7 @@ function install_talib() {
return return
fi fi
cd build_helpers cd build_helpers && ./install_ta-lib.sh && cd ..
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib
sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
./configure --prefix=/usr/local
make
sudo make install
if [ -x "$(command -v apt-get)" ]; then
echo "Updating library path using ldconfig"
sudo ldconfig
fi
cd .. && rm -rf ./ta-lib/
cd ..
} }
function install_mac_newer_python_dependencies() { function install_mac_newer_python_dependencies() {

View File

@ -108,6 +108,13 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
assert hasattr(ex._api_async, 'TestKWARG') assert hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert log_has(asynclogmsg, caplog) assert log_has(asynclogmsg, caplog)
# Test additional headers case
Exchange._headers = {'hello': 'world'}
ex = Exchange(conf)
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert ex._api.headers == {'hello': 'world'}
Exchange._headers = {}
def test_destroy(default_conf, mocker, caplog): def test_destroy(default_conf, mocker, caplog):

View File

@ -739,11 +739,16 @@ def test_auto_hyperopt_interface(default_conf):
PairLocks.timeframe = default_conf['timeframe'] PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
with pytest.raises(OperationalException):
next(strategy.enumerate_parameters('deadBeef'))
assert strategy.buy_rsi.value == strategy.buy_params['buy_rsi'] assert strategy.buy_rsi.value == strategy.buy_params['buy_rsi']
# PlusDI is NOT in the buy-params, so default should be used # PlusDI is NOT in the buy-params, so default should be used
assert strategy.buy_plusdi.value == 0.5 assert strategy.buy_plusdi.value == 0.5
assert strategy.sell_rsi.value == strategy.sell_params['sell_rsi'] assert strategy.sell_rsi.value == strategy.sell_params['sell_rsi']
assert repr(strategy.sell_rsi) == 'IntParameter(74)'
# Parameter is disabled - so value from sell_param dict will NOT be used. # Parameter is disabled - so value from sell_param dict will NOT be used.
assert strategy.sell_minusdi.value == 0.5 assert strategy.sell_minusdi.value == 0.5
all_params = strategy.detect_all_parameters() all_params = strategy.detect_all_parameters()

View File

@ -518,6 +518,7 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order,
# 0 trades, but it's not because of pairlock. # 0 trades, but it's not because of pairlock.
assert n == 0 assert n == 0
assert not log_has_re(message, caplog) assert not log_has_re(message, caplog)
caplog.clear()
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because') PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because')
n = freqtrade.enter_positions() n = freqtrade.enter_positions()
@ -1086,6 +1087,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.is_open is False assert trade.is_open is False
caplog.clear()
mocker.patch( mocker.patch(
'freqtrade.exchange.Binance.stoploss', 'freqtrade.exchange.Binance.stoploss',
@ -1743,10 +1745,12 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
) )
assert not freqtrade.update_trade_state(trade, None) assert not freqtrade.update_trade_state(trade, None)
assert log_has_re(r'Orderid for trade .* is empty.', caplog) assert log_has_re(r'Orderid for trade .* is empty.', caplog)
caplog.clear()
# Add datetime explicitly since sqlalchemy defaults apply only once written to database # Add datetime explicitly since sqlalchemy defaults apply only once written to database
freqtrade.update_trade_state(trade, '123') freqtrade.update_trade_state(trade, '123')
# Test amount not modified by fee-logic # Test amount not modified by fee-logic
assert not log_has_re(r'Applying fee to .*', caplog) assert not log_has_re(r'Applying fee to .*', caplog)
caplog.clear()
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.amount == limit_buy_order['amount'] assert trade.amount == limit_buy_order['amount']
@ -2453,8 +2457,8 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
handle_cancel_buy=MagicMock(), handle_cancel_enter=MagicMock(),
handle_cancel_sell=MagicMock(), handle_cancel_exit=MagicMock(),
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -2475,7 +2479,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
caplog) caplog)
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None: def test_handle_cancel_enter(mocker, caplog, default_conf, limit_buy_order) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
cancel_buy_order = deepcopy(limit_buy_order) cancel_buy_order = deepcopy(limit_buy_order)
@ -2486,7 +2490,7 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
freqtrade._notify_buy_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
@ -2494,46 +2498,46 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
limit_buy_order['filled'] = 0.0 limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open' limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock() cancel_order_mock.reset_mock()
caplog.clear() caplog.clear()
limit_buy_order['filled'] = 0.01 limit_buy_order['filled'] = 0.01
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog) assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
caplog.clear() caplog.clear()
cancel_order_mock.reset_mock() cancel_order_mock.reset_mock()
limit_buy_order['filled'] = 2 limit_buy_order['filled'] = 2
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
# Order remained open for some reason (cancel failed) # Order remained open for some reason (cancel failed)
cancel_buy_order['status'] = 'open' cancel_buy_order['status'] = 'open'
cancel_order_mock = MagicMock(return_value=cancel_buy_order) cancel_order_mock = MagicMock(return_value=cancel_buy_order)
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert log_has_re(r"Order .* for .* not cancelled.", caplog) assert log_has_re(r"Order .* for .* not cancelled.", caplog)
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
indirect=['limit_buy_order_canceled_empty']) indirect=['limit_buy_order_canceled_empty'])
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf, def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf,
limit_buy_order_canceled_empty) -> None: limit_buy_order_canceled_empty) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
cancel_order_mock = mocker.patch( cancel_order_mock = mocker.patch(
'freqtrade.exchange.Exchange.cancel_order_with_result', 'freqtrade.exchange.Exchange.cancel_order_with_result',
return_value=limit_buy_order_canceled_empty) return_value=limit_buy_order_canceled_empty)
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel') nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel')
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/ETH' trade.pair = 'LTC/ETH'
assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog) assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
assert nofiy_mock.call_count == 1 assert nofiy_mock.call_count == 1
@ -2545,8 +2549,8 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
'String Return value', 'String Return value',
123 123
]) ])
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order, def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
cancelorder) -> None: cancelorder) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
cancel_order_mock = MagicMock(return_value=cancelorder) cancel_order_mock = MagicMock(return_value=cancelorder)
@ -2556,7 +2560,7 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
freqtrade._notify_buy_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
@ -2564,16 +2568,16 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
limit_buy_order['filled'] = 0.0 limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open' limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock() cancel_order_mock.reset_mock()
limit_buy_order['filled'] = 1.0 limit_buy_order['filled'] = 1.0
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason) assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None: def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None:
send_msg_mock = patch_RPCManager(mocker) send_msg_mock = patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
@ -2599,26 +2603,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
'amount': 1, 'amount': 1,
'status': "open"} 'status': "open"}
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_sell(trade, order, reason) assert freqtrade.handle_cancel_exit(trade, order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1 assert send_msg_mock.call_count == 1
send_msg_mock.reset_mock() send_msg_mock.reset_mock()
order['amount'] = 2 order['amount'] = 2
assert freqtrade.handle_cancel_sell(trade, order, reason assert freqtrade.handle_cancel_exit(trade, order, reason
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
# Assert cancel_order was not called (callcount remains unchanged) # Assert cancel_order was not called (callcount remains unchanged)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1 assert send_msg_mock.call_count == 1
assert freqtrade.handle_cancel_sell(trade, order, reason assert freqtrade.handle_cancel_exit(trade, order, reason
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
# Message should not be iterated again # Message should not be iterated again
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
assert send_msg_mock.call_count == 1 assert send_msg_mock.call_count == 1
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None: def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch( mocker.patch(
@ -2631,7 +2635,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
order = {'remaining': 1, order = {'remaining': 1,
'amount': 1, 'amount': 1,
'status': "open"} 'status': "open"}
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order' assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
@ -3303,7 +3307,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
assert trade.amount != amnt assert trade.amount != amnt
def test__safe_sell_amount(default_conf, fee, caplog, mocker): def test__safe_exit_amount(default_conf, fee, caplog, mocker):
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
amount = 95.33 amount = 95.33
@ -3323,17 +3327,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
wallet_update.reset_mock() wallet_update.reset_mock()
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
assert log_has_re(r'.*Falling back to wallet-amount.', caplog) assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1 assert wallet_update.call_count == 1
caplog.clear() caplog.clear()
wallet_update.reset_mock() wallet_update.reset_mock()
assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog) assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1 assert wallet_update.call_count == 1
def test__safe_sell_amount_error(default_conf, fee, caplog, mocker): def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
amount = 95.33 amount = 95.33
@ -3351,7 +3355,7 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
with pytest.raises(DependencyException, match=r"Not enough amount to exit."): with pytest.raises(DependencyException, match=r"Not enough amount to exit."):
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None: def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
@ -3525,6 +3529,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, limit_buy_or
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog) assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
assert log_has("ETH/BTC - Adjusting stoploss...", caplog) assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
assert trade.stop_loss == 0.0000138501 assert trade.stop_loss == 0.0000138501
caplog.clear()
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={ MagicMock(return_value={
@ -3585,6 +3590,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%", caplog) assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%", caplog)
assert log_has("ETH/BTC - Adjusting stoploss...", caplog) assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
assert trade.stop_loss == 0.0000138501 assert trade.stop_loss == 0.0000138501
caplog.clear()
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={ MagicMock(return_value={
@ -3649,6 +3655,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, limit_buy_order_
assert not log_has("ETH/BTC - Adjusting stoploss...", caplog) assert not log_has("ETH/BTC - Adjusting stoploss...", caplog)
assert trade.stop_loss == 0.0000098910 assert trade.stop_loss == 0.0000098910
caplog.clear()
# price rises above the offset (rises 12% when the offset is 5.5%) # price rises above the offset (rises 12% when the offset is 5.5%)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
@ -4316,8 +4323,8 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
mocker.patch('freqtrade.exchange.Exchange.fetch_order', mocker.patch('freqtrade.exchange.Exchange.fetch_order',
side_effect=[ side_effect=[
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order]) ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy') buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell') sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee) create_mock_trades(fee)
@ -4351,6 +4358,7 @@ def test_update_open_orders(mocker, default_conf, fee, caplog):
freqtrade.update_open_orders() freqtrade.update_open_orders()
assert not log_has_re(r"Error updating Order .*", caplog) assert not log_has_re(r"Error updating Order .*", caplog)
caplog.clear()
freqtrade.config['dry_run'] = False freqtrade.config['dry_run'] = False
freqtrade.update_open_orders() freqtrade.update_open_orders()
@ -4432,14 +4440,14 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf, fee):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog): def test_reupdate_enter_order_fees(mocker, default_conf, fee, caplog):
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
create_mock_trades(fee) create_mock_trades(fee)
trades = Trade.get_trades().all() trades = Trade.get_trades().all()
freqtrade.reupdate_buy_order_fees(trades[0]) freqtrade.reupdate_enter_order_fees(trades[0])
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
assert mock_uts.call_count == 1 assert mock_uts.call_count == 1
assert mock_uts.call_args_list[0][0][0] == trades[0] assert mock_uts.call_args_list[0][0][0] == trades[0]
@ -4462,7 +4470,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
) )
Trade.query.session.add(trade) Trade.query.session.add(trade)
freqtrade.reupdate_buy_order_fees(trade) freqtrade.reupdate_enter_order_fees(trade)
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
assert mock_uts.call_count == 0 assert mock_uts.call_count == 0
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog) assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
@ -4472,7 +4480,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
def test_handle_insufficient_funds(mocker, default_conf, fee): def test_handle_insufficient_funds(mocker, default_conf, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order') mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_buy_order_fees') mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
create_mock_trades(fee) create_mock_trades(fee)
trades = Trade.get_trades().all() trades = Trade.get_trades().all()

View File

@ -70,7 +70,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True), create_stoploss_order=MagicMock(return_value=True),
_notify_sell=MagicMock(), _notify_exit=MagicMock(),
) )
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock) mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
@ -154,7 +154,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True), create_stoploss_order=MagicMock(return_value=True),
_notify_sell=MagicMock(), _notify_exit=MagicMock(),
) )
should_sell_mock = MagicMock(side_effect=[ should_sell_mock = MagicMock(side_effect=[
SellCheckTuple(sell_type=SellType.NONE), SellCheckTuple(sell_type=SellType.NONE),