Merged with develop
This commit is contained in:
commit
1f38088d7b
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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.*
|
||||||
|
@ -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
|
||||||
|
@ -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 \
|
|
||||||
|
@ -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')]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
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 sell-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:
|
||||||
@ -166,14 +166,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 forcesell.
|
# Protect from collisions with forcesell.
|
||||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||||
# while selling 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)
|
||||||
@ -296,9 +296,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.
|
||||||
@ -476,21 +476,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:
|
||||||
@ -498,7 +498,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)
|
||||||
|
|
||||||
@ -508,27 +508,27 @@ 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
|
||||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||||
|
|
||||||
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':
|
||||||
@ -551,13 +551,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')
|
||||||
@ -569,8 +569,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,
|
||||||
@ -590,11 +590,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 buy occurred.
|
Sends rpc notification when a buy occurred.
|
||||||
"""
|
"""
|
||||||
@ -617,7 +617,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 buy cancel occurred.
|
Sends rpc notification when a buy cancel occurred.
|
||||||
"""
|
"""
|
||||||
@ -643,7 +643,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,
|
||||||
@ -713,8 +713,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)
|
||||||
@ -744,7 +744,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
except InvalidOrderException as e:
|
except InvalidOrderException as e:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Selling the trade forcefully')
|
logger.warning('Exiting the trade forcefully')
|
||||||
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||||
sell_type=SellType.EMERGENCY_SELL))
|
sell_type=SellType.EMERGENCY_SELL))
|
||||||
|
|
||||||
@ -782,7 +782,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:
|
||||||
@ -851,19 +851,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
|
||||||
|
|
||||||
@ -906,7 +906,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
|
||||||
@ -915,7 +915,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:
|
||||||
"""
|
"""
|
||||||
@ -931,13 +931,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
|
||||||
@ -994,11 +994,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||||
|
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||||
reason=reason)
|
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:
|
||||||
"""
|
"""
|
||||||
Sell cancel - cancel order and update trade
|
Sell cancel - cancel order and update trade
|
||||||
:return: Reason for cancel
|
:return: Reason for cancel
|
||||||
@ -1032,14 +1032,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.
|
||||||
@ -1111,7 +1111,7 @@ 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']
|
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)(
|
||||||
@ -1150,11 +1150,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.
|
||||||
"""
|
"""
|
||||||
@ -1196,7 +1196,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.
|
||||||
"""
|
"""
|
||||||
@ -1291,13 +1291,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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
]
|
]
|
||||||
|
@ -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())
|
||||||
|
@ -403,8 +403,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,
|
||||||
@ -545,12 +548,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
|
||||||
@ -563,7 +566,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():
|
||||||
@ -625,7 +628,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:
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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": [
|
||||||
]
|
]
|
||||||
|
@ -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": [
|
||||||
|
|
||||||
|
18
freqtrade/templates/subtemplates/exchange_kucoin.j2
Normal file
18
freqtrade/templates/subtemplates/exchange_kucoin.j2
Normal 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": [
|
||||||
|
]
|
||||||
|
}
|
72
mkdocs.yml
72
mkdocs.yml
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
14
setup.sh
14
setup.sh
@ -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() {
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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',
|
||||||
@ -1190,7 +1192,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
|||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||||
assert log_has("Selling the trade forcefully", caplog)
|
assert log_has("Exiting the trade forcefully", caplog)
|
||||||
|
|
||||||
# Should call a market sell
|
# Should call a market sell
|
||||||
assert create_order_mock.call_count == 2
|
assert create_order_mock.call_count == 2
|
||||||
@ -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 sell."):
|
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
|
||||||
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()
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user