Merge branch 'develop' into startup-time

This commit is contained in:
Meng Xiangzhuo
2024-10-04 08:38:55 +08:00
36 changed files with 2902 additions and 1472 deletions
+4 -7
View File
@@ -25,7 +25,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ] os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ]
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -72,7 +72,7 @@ jobs:
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- name: Coveralls - name: Coveralls
if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04') if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04')
env: env:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories # Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
@@ -139,10 +139,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ "macos-12", "macos-13", "macos-14" ] os: [ "macos-12", "macos-13", "macos-14" ]
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12"]
exclude:
- os: "macos-14"
python-version: "3.9"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -263,7 +260,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ windows-latest ] os: [ windows-latest ]
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: 'v0.6.7' rev: 'v0.6.8'
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.12.6-slim-bookworm as base FROM python:3.12.7-slim-bookworm as base
# Setup env # Setup env
ENV LANG C.UTF-8 ENV LANG C.UTF-8
+2 -2
View File
@@ -61,7 +61,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
## Features ## Features
- [x] **Based on Python 3.9+**: For botting on any operating system - Windows, macOS and Linux. - [x] **Based on Python 3.10+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Persistence**: Persistence is achieved through sqlite. - [x] **Persistence**: Persistence is achieved through sqlite.
- [x] **Dry-run**: Run the bot without paying money. - [x] **Dry-run**: Run the bot without paying money.
- [x] **Backtesting**: Run a simulation of your buy/sell strategy. - [x] **Backtesting**: Run a simulation of your buy/sell strategy.
@@ -218,7 +218,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements ### Software requirements
- [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/) - [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
- [pip](https://pip.pypa.io/en/stable/installing/) - [pip](https://pip.pypa.io/en/stable/installing/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [TA-Lib](https://ta-lib.github.io/ta-lib-python/) - [TA-Lib](https://ta-lib.github.io/ta-lib-python/)
Binary file not shown.
+1 -1
View File
@@ -85,7 +85,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
Alternatively Alternatively
- Python 3.9+ - Python 3.10+
- pip (pip3) - pip (pip3)
- git - git
- TA-Lib - TA-Lib
+5 -5
View File
@@ -24,7 +24,7 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito
The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
!!! Note !!! Note
Python3.9 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. Python3.10 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully. Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully.
!!! Warning "Up-to-date clock" !!! Warning "Up-to-date clock"
@@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and
### Install guide ### Install guide
* [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/) * [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/) * [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) * [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
@@ -54,7 +54,7 @@ We've included/collected install instructions for Ubuntu, MacOS, and Windows. Th
OS Specific steps are listed first, the common section below is necessary for all systems. OS Specific steps are listed first, the common section below is necessary for all systems.
!!! Note !!! Note
Python3.9 or higher and the corresponding pip are assumed to be available. Python3.10 or higher and the corresponding pip are assumed to be available.
=== "Debian/Ubuntu" === "Debian/Ubuntu"
#### Install necessary dependencies #### Install necessary dependencies
@@ -69,7 +69,7 @@ OS Specific steps are listed first, the common section below is necessary for al
=== "RaspberryPi/Raspbian" === "RaspberryPi/Raspbian"
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/). The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
This image comes with python3.9 preinstalled, making it easy to get freqtrade up and running. This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running.
Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied.
@@ -169,7 +169,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr
** --install ** ** --install **
With this option, the script will install the bot and most dependencies: With this option, the script will install the bot and most dependencies:
You will need to have git and python3.9+ installed beforehand for this to work. You will need to have git and python3.10+ installed beforehand for this to work.
* Mandatory software as: `ta-lib` * Mandatory software as: `ta-lib`
* Setup your virtualenv under `.venv/` * Setup your virtualenv under `.venv/`
+1 -1
View File
@@ -231,7 +231,7 @@ Once all positions are sold, run `/stop` to completely stop the bot.
`/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command. `/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command.
!!! Warning !!! Warning
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
### /status ### /status
+2 -2
View File
@@ -5,7 +5,7 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md)
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
Otherwise, please follow the instructions below. Otherwise, please follow the instructions below.
All instructions assume that python 3.9+ is installed and available. All instructions assume that python 3.10+ is installed and available.
## Clone the git repository ## Clone the git repository
@@ -42,7 +42,7 @@ cd freqtrade
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows). Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10, 3.11 and 3.12) and for 64bit Windows. As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.10, 3.11 and 3.12) and for 64bit Windows.
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade. These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
Other versions must be downloaded from the above link. Other versions must be downloaded from the above link.
+1 -1
View File
@@ -3,7 +3,7 @@
__main__.py for Freqtrade __main__.py for Freqtrade
To launch Freqtrade as a module To launch Freqtrade as a module
> python -m freqtrade (with Python >= 3.9) > python -m freqtrade (with Python >= 3.10)
""" """
from freqtrade import main from freqtrade import main
@@ -1,5 +1,4 @@
import logging import logging
import sys
import time import time
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
@@ -19,9 +18,6 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
if sys.version_info == (3, 8): # pragma: no cover
sys.exit("Freqtrade strategy updater requires Python version >= 3.9")
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
strategy_objs = StrategyResolver.search_all_objects( strategy_objs = StrategyResolver.search_all_objects(
+54 -5
View File
@@ -144,6 +144,29 @@ class Binance(Exchange):
""" """
return open_date.minute == 0 and open_date.second < 15 return open_date.minute == 0 and open_date.second < 15
def fetch_funding_rates(
self, symbols: Optional[List[str]] = None
) -> Dict[str, Dict[str, float]]:
"""
Fetch funding rates for the given symbols.
:param symbols: List of symbols to fetch funding rates for
:return: Dict of funding rates for the given symbols
"""
try:
if self.trading_mode == TradingMode.FUTURES:
rates = self._api.fetch_funding_rates(symbols)
return rates
return {}
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def dry_run_liquidation_price( def dry_run_liquidation_price(
self, self,
pair: str, pair: str,
@@ -153,8 +176,7 @@ class Binance(Exchange):
stake_amount: float, stake_amount: float,
leverage: float, leverage: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only open_trades: list,
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting! Important: Must be fetching data from cached values as this is used by backtesting!
@@ -172,6 +194,7 @@ class Binance(Exchange):
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of open trades in the same wallet
# * Only required for Cross # * Only required for Cross
:param mm_ex_1: (TMM) :param mm_ex_1: (TMM)
@@ -180,15 +203,41 @@ class Binance(Exchange):
:param upnl_ex_1: (UPNL) :param upnl_ex_1: (UPNL)
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1. Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0 Isolated-Margin Mode: 0
:param other
""" """
cross_vars: float = 0.0
side_1 = -1 if is_short else 1
cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100% # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position # maintenance_amt: (CUM) Maintenance Amount of position
mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount) mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if self.margin_mode == MarginMode.CROSS:
mm_ex_1: float = 0.0
upnl_ex_1: float = 0.0
pairs = [trade.pair for trade in open_trades]
if self._config["runmode"] in ("live", "dry_run"):
funding_rates = self.fetch_funding_rates(pairs)
for trade in open_trades:
if trade.pair == pair:
# Only "other" trades are considered
continue
if self._config["runmode"] in ("live", "dry_run"):
mark_price = funding_rates[trade.pair]["markPrice"]
else:
# Fall back to open rate for backtesting
mark_price = trade.open_rate
mm_ratio1, maint_amnt1 = self.get_maintenance_ratio_and_amt(
trade.pair, trade.stake_amount
)
maint_margin = trade.amount * mark_price * mm_ratio1 - maint_amnt1
mm_ex_1 += maint_margin
upnl_ex_1 += trade.amount * mark_price - trade.amount * trade.open_rate
cross_vars = upnl_ex_1 - mm_ex_1
side_1 = -1 if is_short else 1
if maintenance_amt is None: if maintenance_amt is None:
raise OperationalException( raise OperationalException(
"Parameter maintenance_amt is required by Binance.liquidation_price" "Parameter maintenance_amt is required by Binance.liquidation_price"
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -147,8 +147,7 @@ class Bybit(Exchange):
stake_amount: float, stake_amount: float,
leverage: float, leverage: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only open_trades: list,
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting! Important: Must be fetching data from cached values as this is used by backtesting!
@@ -178,6 +177,7 @@ class Bybit(Exchange):
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of other open trades in the same wallet
""" """
market = self.markets[pair] market = self.markets[pair]
+4 -10
View File
@@ -3532,8 +3532,7 @@ class Exchange:
stake_amount: float, stake_amount: float,
leverage: float, leverage: float,
wallet_balance: float, wallet_balance: float,
mm_ex_1: float = 0.0, # (Binance) Cross only open_trades: Optional[list] = None,
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Set's the margin mode on the exchange to cross or isolated for a specific pair Set's the margin mode on the exchange to cross or isolated for a specific pair
@@ -3555,8 +3554,7 @@ class Exchange:
leverage=leverage, leverage=leverage,
stake_amount=stake_amount, stake_amount=stake_amount,
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1, open_trades=open_trades or [],
upnl_ex_1=upnl_ex_1,
) )
else: else:
positions = self.fetch_positions(pair) positions = self.fetch_positions(pair)
@@ -3582,8 +3580,7 @@ class Exchange:
stake_amount: float, stake_amount: float,
leverage: float, leverage: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only open_trades: list,
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting! Important: Must be fetching data from cached values as this is used by backtesting!
@@ -3608,10 +3605,7 @@ class Exchange:
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of other open trades in the same wallet
# * Not required by Gate or OKX
:param mm_ex_1:
:param upnl_ex_1:
""" """
market = self.markets[pair] market = self.markets[pair]
+19 -14
View File
@@ -43,6 +43,7 @@ from freqtrade.exchange import (
timeframe_to_next_date, timeframe_to_next_date,
timeframe_to_seconds, timeframe_to_seconds,
) )
from freqtrade.leverage.liquidation_price import update_liquidation_prices
from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin from freqtrade.mixins import LoggingMixin
from freqtrade.persistence import Order, PairLocks, Trade, init_db from freqtrade.persistence import Order, PairLocks, Trade, init_db
@@ -241,6 +242,7 @@ class FreqtradeBot(LoggingMixin):
# Only update open orders on startup # Only update open orders on startup
# This will update the database after the initial migration # This will update the database after the initial migration
self.startup_update_open_orders() self.startup_update_open_orders()
self.update_all_liquidation_prices()
self.update_funding_fees() self.update_funding_fees()
def process(self) -> None: def process(self) -> None:
@@ -357,6 +359,16 @@ class FreqtradeBot(LoggingMixin):
open_trades = Trade.get_open_trade_count() open_trades = Trade.get_open_trade_count()
return max(0, self.config["max_open_trades"] - open_trades) return max(0, self.config["max_open_trades"] - open_trades)
def update_all_liquidation_prices(self) -> None:
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.CROSS:
# Update liquidation prices for all trades in cross margin mode
update_liquidation_prices(
exchange=self.exchange,
wallets=self.wallets,
stake_currency=self.config["stake_currency"],
dry_run=self.config["dry_run"],
)
def update_funding_fees(self) -> None: def update_funding_fees(self) -> None:
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
trades: List[Trade] = Trade.get_open_trades() trades: List[Trade] = Trade.get_open_trades()
@@ -2233,20 +2245,13 @@ class FreqtradeBot(LoggingMixin):
# Must also run for partial exits # Must also run for partial exits
# TODO: Margin will need to use interest_rate as well. # TODO: Margin will need to use interest_rate as well.
# interest_rate = self.exchange.get_interest_rate() # interest_rate = self.exchange.get_interest_rate()
try: update_liquidation_prices(
trade.set_liquidation_price( trade,
self.exchange.get_liquidation_price( exchange=self.exchange,
pair=trade.pair, wallets=self.wallets,
open_rate=trade.open_rate, stake_currency=self.config["stake_currency"],
is_short=trade.is_short, dry_run=self.config["dry_run"],
amount=trade.amount, )
stake_amount=trade.stake_amount,
leverage=trade.leverage,
wallet_balance=trade.stake_amount,
)
)
except DependencyException:
logger.warning("Unable to calculate liquidation price")
if self.strategy.use_custom_stoploss: if self.strategy.use_custom_stoploss:
current_rate = self.exchange.get_rate( current_rate = self.exchange.get_rate(
trade.pair, side="exit", is_short=trade.is_short, refresh=True trade.pair, side="exit", is_short=trade.is_short, refresh=True
+66
View File
@@ -0,0 +1,66 @@
import logging
from typing import Optional
from freqtrade.enums import MarginMode
from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange
from freqtrade.persistence import LocalTrade, Trade
from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__)
def update_liquidation_prices(
trade: Optional[LocalTrade] = None,
*,
exchange: Exchange,
wallets: Wallets,
stake_currency: str,
dry_run: bool = False,
):
"""
Update trade liquidation price in isolated margin mode.
Updates liquidation price for all trades in cross margin mode.
"""
try:
if exchange.margin_mode == MarginMode.CROSS:
total_wallet_stake = 0.0
if dry_run:
# Parameters only needed for cross margin
total_wallet_stake = wallets.get_total(stake_currency)
logger.info("Updating liquidation price for all open trades.")
open_trades = Trade.get_open_trades()
for t in open_trades:
# TODO: This should be done in a batch update
t.set_liquidation_price(
exchange.get_liquidation_price(
pair=t.pair,
open_rate=t.open_rate,
is_short=t.is_short,
amount=t.amount,
stake_amount=t.stake_amount,
leverage=t.leverage,
wallet_balance=total_wallet_stake,
open_trades=open_trades,
)
)
elif trade:
trade.set_liquidation_price(
exchange.get_liquidation_price(
pair=trade.pair,
open_rate=trade.open_rate,
is_short=trade.is_short,
amount=trade.amount,
stake_amount=trade.stake_amount,
leverage=trade.leverage,
wallet_balance=trade.stake_amount,
)
)
else:
raise DependencyException(
"Trade object is required for updating liquidation price in isolated margin mode."
)
except DependencyException:
logger.warning("Unable to calculate liquidation price")
+2 -2
View File
@@ -10,8 +10,8 @@ from typing import Any, List, Optional
# check min. python version # check min. python version
if sys.version_info < (3, 9): # pragma: no cover if sys.version_info < (3, 10): # pragma: no cover
sys.exit("Freqtrade requires Python version >= 3.9") sys.exit("Freqtrade requires Python version >= 3.10")
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
+15 -13
View File
@@ -26,6 +26,7 @@ from freqtrade.enums import (
CandleType, CandleType,
ExitCheckTuple, ExitCheckTuple,
ExitType, ExitType,
MarginMode,
RunMode, RunMode,
TradingMode, TradingMode,
) )
@@ -37,6 +38,7 @@ from freqtrade.exchange import (
) )
from freqtrade.exchange.exchange import Exchange from freqtrade.exchange.exchange import Exchange
from freqtrade.ft_types import BacktestResultType, get_BacktestResultType_default from freqtrade.ft_types import BacktestResultType, get_BacktestResultType_default
from freqtrade.leverage.liquidation_price import update_liquidation_prices
from freqtrade.mixins import LoggingMixin from freqtrade.mixins import LoggingMixin
from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.backtest_caching import get_strategy_run_id
from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.bt_progress import BTProgress
@@ -206,6 +208,7 @@ class Backtesting:
self.required_startup = self.dataprovider.get_required_startup(self.timeframe) self.required_startup = self.dataprovider.get_required_startup(self.timeframe)
self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT) self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT)
self.margin_mode: MarginMode = config.get("margin_mode", MarginMode.ISOLATED)
# strategies which define "can_short=True" will fail to load in Spot mode. # strategies which define "can_short=True" will fail to load in Spot mode.
self._can_short = self.trading_mode != TradingMode.SPOT self._can_short = self.trading_mode != TradingMode.SPOT
self._position_stacking: bool = self.config.get("position_stacking", False) self._position_stacking: bool = self.config.get("position_stacking", False)
@@ -698,21 +701,20 @@ class Backtesting:
current_time=current_date, current_time=current_date,
) )
if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount): if self.margin_mode == MarginMode.CROSS or not (
# trade is still open order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount
trade.set_liquidation_price( ):
self.exchange.get_liquidation_price( # trade is still open or we are in cross margin mode and
pair=trade.pair, # must update all liquidation prices
open_rate=trade.open_rate, update_liquidation_prices(
is_short=trade.is_short, trade,
amount=trade.amount, exchange=self.exchange,
stake_amount=trade.stake_amount, wallets=self.wallets,
leverage=trade.leverage, stake_currency=self.config["stake_currency"],
wallet_balance=trade.stake_amount, dry_run=self.config["dry_run"],
)
) )
if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount):
self._call_adjust_stop(current_date, trade, order.ft_price) self._call_adjust_stop(current_date, trade, order.ft_price)
# pass
return True return True
return False return False
+1 -1
View File
@@ -760,7 +760,7 @@ class LocalTrade:
Method you should use to set self.liquidation price. Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price Assures stop_loss is not passed the liquidation price
""" """
if not liquidation_price: if liquidation_price is None:
return return
self.liquidation_price = liquidation_price self.liquidation_price = liquidation_price
+1 -11
View File
@@ -31,16 +31,6 @@ async def ui_version():
} }
def is_relative_to(path: Path, base: Path) -> bool:
# Helper function simulating behaviour of is_relative_to, which was only added in python 3.9
try:
path.relative_to(base)
return True
except ValueError:
pass
return False
@router_ui.get("/{rest_of_path:path}", include_in_schema=False) @router_ui.get("/{rest_of_path:path}", include_in_schema=False)
async def index_html(rest_of_path: str): async def index_html(rest_of_path: str):
""" """
@@ -56,7 +46,7 @@ async def index_html(rest_of_path: str):
if filename.suffix == ".js": if filename.suffix == ".js":
# Force text/javascript for .js files - Circumvent faulty system configuration # Force text/javascript for .js files - Circumvent faulty system configuration
media_type = "application/javascript" media_type = "application/javascript"
if filename.is_file() and is_relative_to(filename, uibase): if filename.is_file() and filename.is_relative_to(uibase):
return FileResponse(str(filename), media_type=media_type) return FileResponse(str(filename), media_type=media_type)
index_file = uibase / "index.html" index_file = uibase / "index.html"
+1 -2
View File
@@ -13,14 +13,13 @@ authors = [
description = "Freqtrade - Client scripts" description = "Freqtrade - Client scripts"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.10"
license = {text = "GPLv3"} license = {text = "GPLv3"}
# license = "GPLv3" # license = "GPLv3"
classifiers = [ classifiers = [
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Science/Research", "Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
+1 -4
View File
@@ -13,14 +13,12 @@ authors = [
description = "Freqtrade - Crypto Trading Bot" description = "Freqtrade - Crypto Trading Bot"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.10"
license = {text = "GPLv3"} license = {text = "GPLv3"}
# license = "GPLv3"
classifiers = [ classifiers = [
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Science/Research", "Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
@@ -125,7 +123,6 @@ extend-exclude = [".env", ".venv"]
target-version = "py38" target-version = "py38"
[tool.ruff.lint] [tool.ruff.lint]
# Exclude UP036 as it's causing the "exit if < 3.9" to fail.
extend-select = [ extend-select = [
"C90", # mccabe "C90", # mccabe
"B", # bugbear "B", # bugbear
+1 -2
View File
@@ -2,8 +2,7 @@
-r requirements.txt -r requirements.txt
# Required for hyperopt # Required for hyperopt
scipy==1.14.1; python_version >= "3.10" scipy==1.14.1
scipy==1.13.1; python_version < "3.10"
scikit-learn==1.5.2 scikit-learn==1.5.2
ft-scikit-optimize==0.9.2 ft-scikit-optimize==0.9.2
filelock==3.16.1 filelock==3.16.1
+1 -3
View File
@@ -22,9 +22,7 @@ technical==1.4.4
tabulate==0.9.0 tabulate==0.9.0
pycoingecko==3.1.0 pycoingecko==3.1.0
jinja2==3.1.4 jinja2==3.1.4
# Tables 3.10 dropped support for Python 3.9 tables==3.10.1
tables==3.9.1; python_version < "3.10"
tables==3.10.1; python_version >= "3.10"
joblib==1.4.2 joblib==1.4.2
rich==13.8.1 rich==13.8.1
pyarrow==17.0.0; platform_machine != 'armv7l' pyarrow==17.0.0; platform_machine != 'armv7l'
+3 -6
View File
@@ -153,16 +153,13 @@ function Find-PythonExecutable {
"python3.12", "python3.12",
"python3.11", "python3.11",
"python3.10", "python3.10",
"python3.9",
"python3", "python3",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe",
"C:\Python312\python.exe", "C:\Python312\python.exe",
"C:\Python311\python.exe", "C:\Python311\python.exe",
"C:\Python310\python.exe", "C:\Python310\python.exe"
"C:\Python39\python.exe"
) )
@@ -178,10 +175,10 @@ function Main {
"Starting the operations..." | Out-File $LogFilePath -Append "Starting the operations..." | Out-File $LogFilePath -Append
"Current directory: $(Get-Location)" | Out-File $LogFilePath -Append "Current directory: $(Get-Location)" | Out-File $LogFilePath -Append
# Exit on lower versions than Python 3.9 or when Python executable not found # Exit on lower versions than Python 3.10 or when Python executable not found
$PythonExecutable = Find-PythonExecutable $PythonExecutable = Find-PythonExecutable
if ($null -eq $PythonExecutable) { if ($null -eq $PythonExecutable) {
Write-Log "No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH." -Level 'ERROR' Write-Log "No suitable Python executable found. Please ensure that Python 3.10 or higher is installed and available in the system PATH." -Level 'ERROR'
Exit 1 Exit 1
} }
+5 -5
View File
@@ -25,7 +25,7 @@ function check_installed_python() {
exit 2 exit 2
fi fi
for v in 12 11 10 9 for v in 12 11 10
do do
PYTHON="python3.${v}" PYTHON="python3.${v}"
which $PYTHON which $PYTHON
@@ -36,7 +36,7 @@ function check_installed_python() {
fi fi
done done
echo "No usable python found. Please make sure to have python3.9 or newer installed." echo "No usable python found. Please make sure to have python3.10 or newer installed."
exit 1 exit 1
} }
@@ -166,7 +166,7 @@ function install_macos() {
#Gets number after decimal in python version #Gets number after decimal in python version
version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g') version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g')
if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9 if [[ $version -ge 10 ]]; then #Checks if python version >= 3.10
install_mac_newer_python_dependencies install_mac_newer_python_dependencies
fi fi
} }
@@ -277,7 +277,7 @@ function install() {
install_redhat install_redhat
else else
echo "This script does not support your OS." echo "This script does not support your OS."
echo "If you have Python version 3.9 - 3.12, pip, virtualenv, ta-lib you can continue." echo "If you have Python version 3.10 - 3.12, pip, virtualenv, ta-lib you can continue."
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
sleep 10 sleep 10
fi fi
@@ -304,7 +304,7 @@ function help() {
echo " -p,--plot Install dependencies for Plotting scripts." echo " -p,--plot Install dependencies for Plotting scripts."
} }
# Verify if 3.9+ is installed # Verify if 3.10+ is installed
check_installed_python check_installed_python
case $* in case $* in
+2 -2
View File
@@ -38,7 +38,7 @@ def mock_trade_1(fee, is_short: bool):
trade = Trade( trade = Trade(
pair="ETH/BTC", pair="ETH/BTC",
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=50.0,
amount_requested=123.0, amount_requested=123.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
@@ -201,7 +201,7 @@ def mock_trade_4(fee, is_short: bool):
trade = Trade( trade = Trade(
pair="ETC/BTC", pair="ETC/BTC",
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=0.0,
amount_requested=124.0, amount_requested=124.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
+1 -1
View File
@@ -224,7 +224,7 @@ def mock_trade_usdt_4(fee, is_short: bool):
trade = Trade( trade = Trade(
pair="NEO/USDT", pair="NEO/USDT",
stake_amount=20.0, stake_amount=20.0,
amount=10.0, amount=0.0,
amount_requested=10.01, amount_requested=10.01,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
+93 -22
View File
@@ -7,6 +7,7 @@ import pytest
from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from freqtrade.persistence import Trade
from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re
from tests.exchange.test_exchange import ccxt_exceptionhandlers from tests.exchange.test_exchange import ccxt_exceptionhandlers
@@ -171,59 +172,101 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"is_short, trading_mode, margin_mode, wallet_balance, " "pair, is_short, trading_mode, margin_mode, wallet_balance, "
"mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " "maintenance_amt, amount, open_rate, open_trades,"
"mm_ratio, expected", "mm_ratio, expected",
[ [
( (
"ETH/USDT:USDT",
False, False,
"futures", "futures",
"isolated", "isolated",
1535443.01, 1535443.01,
0.0,
0.0,
135365.00, 135365.00,
3683.979, 3683.979,
1456.84, 1456.84,
[],
0.10, 0.10,
1114.78, 1114.78,
), ),
( (
"ETH/USDT:USDT",
False, False,
"futures", "futures",
"isolated", "isolated",
1535443.01, 1535443.01,
0.0,
0.0,
16300.000, 16300.000,
109.488, 109.488,
32481.980, 32481.980,
[],
0.025, 0.025,
18778.73, 18778.73,
), ),
( (
"ETH/USDT:USDT",
False, False,
"futures", "futures",
"cross", "cross",
1535443.01, 1535443.01,
71200.81144,
-56354.57,
135365.00, 135365.00,
3683.979, 3683.979, # amount
1456.84, 1456.84, # open_rate
[
{
# From calc example
"pair": "BTC/USDT:USDT",
"open_rate": 32481.98,
"amount": 109.488,
"stake_amount": 3556387.02624, # open_rate * amount
"mark_price": 31967.27,
"mm_ratio": 0.025,
"maintenance_amt": 16300.0,
},
{
# From calc example
"pair": "ETH/USDT:USDT",
"open_rate": 1456.84,
"amount": 3683.979,
"stake_amount": 5366967.96,
"mark_price": 1335.18,
"mm_ratio": 0.10,
"maintenance_amt": 135365.00,
},
],
0.10, 0.10,
1153.26, 1153.26,
), ),
( (
"BTC/USDT:USDT",
False, False,
"futures", "futures",
"cross", "cross",
1535443.01, 1535443.01,
356512.508, 16300.0,
-448192.89, 109.488, # amount
16300.000, 32481.980, # open_rate
109.488, [
32481.980, {
# From calc example
"pair": "BTC/USDT:USDT",
"open_rate": 32481.98,
"amount": 109.488,
"stake_amount": 3556387.02624, # open_rate * amount
"mark_price": 31967.27,
"mm_ratio": 0.025,
"maintenance_amt": 16300.0,
},
{
# From calc example
"pair": "ETH/USDT:USDT",
"open_rate": 1456.84,
"amount": 3683.979,
"stake_amount": 5366967.96,
"mark_price": 1335.18,
"mm_ratio": 0.10,
"maintenance_amt": 135365.00,
},
],
0.025, 0.025,
26316.89, 26316.89,
), ),
@@ -232,15 +275,15 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
def test_liquidation_price_binance( def test_liquidation_price_binance(
mocker, mocker,
default_conf, default_conf,
open_rate, pair,
is_short, is_short,
trading_mode, trading_mode,
margin_mode, margin_mode,
wallet_balance, wallet_balance,
mm_ex_1,
upnl_ex_1,
maintenance_amt, maintenance_amt,
amount, amount,
open_rate,
open_trades,
mm_ratio, mm_ratio,
expected, expected,
): ):
@@ -248,20 +291,48 @@ def test_liquidation_price_binance(
default_conf["margin_mode"] = margin_mode default_conf["margin_mode"] = margin_mode
default_conf["liquidation_buffer"] = 0.0 default_conf["liquidation_buffer"] = 0.0
exchange = get_patched_exchange(mocker, default_conf, exchange="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt))
def get_maint_ratio(pair_, stake_amount):
if pair_ != pair:
oc = [c for c in open_trades if c["pair"] == pair_][0]
return oc["mm_ratio"], oc["maintenance_amt"]
return mm_ratio, maintenance_amt
def fetch_funding_rates(*args, **kwargs):
return {
t["pair"]: {
"symbol": t["pair"],
"markPrice": t["mark_price"],
}
for t in open_trades
}
exchange.get_maintenance_ratio_and_amt = get_maint_ratio
exchange.fetch_funding_rates = fetch_funding_rates
open_trade_objects = [
Trade(
pair=t["pair"],
open_rate=t["open_rate"],
amount=t["amount"],
stake_amount=t["stake_amount"],
fee_open=0,
)
for t in open_trades
]
assert ( assert (
pytest.approx( pytest.approx(
round( round(
exchange.get_liquidation_price( exchange.get_liquidation_price(
pair="DOGE/USDT", pair=pair,
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1,
amount=amount, amount=amount,
stake_amount=open_rate * amount, stake_amount=open_rate * amount,
leverage=5, leverage=5,
open_trades=open_trade_objects,
), ),
2, 2,
) )
+2 -2
View File
@@ -5524,8 +5524,6 @@ def test_liquidation_price_is_none(
stake_amount=open_rate * 71200.81144, stake_amount=open_rate * 71200.81144,
leverage=5, leverage=5,
wallet_balance=-56354.57, wallet_balance=-56354.57,
mm_ex_1=0.10,
upnl_ex_1=0.0,
) )
is None is None
) )
@@ -6011,6 +6009,7 @@ def test_get_liquidation_price1(mocker, default_conf):
stake_amount=18.884 * 0.8, stake_amount=18.884 * 0.8,
leverage=leverage, leverage=leverage,
wallet_balance=18.884 * 0.8, wallet_balance=18.884 * 0.8,
open_trades=[],
) )
@@ -6141,6 +6140,7 @@ def test_get_liquidation_price(
wallet_balance=amount * open_rate / leverage, wallet_balance=amount * open_rate / leverage,
leverage=leverage, leverage=leverage,
is_short=is_short, is_short=is_short,
open_trades=[],
) )
if expected_liq is None: if expected_liq is None:
assert liq is None assert liq is None
@@ -457,6 +457,7 @@ class TestCCXTExchange:
stake_amount=100, stake_amount=100,
leverage=5, leverage=5,
wallet_balance=100, wallet_balance=100,
open_trades=[],
) )
assert isinstance(liquidation_price, float) assert isinstance(liquidation_price, float)
assert liquidation_price >= 0.0 assert liquidation_price >= 0.0
@@ -469,6 +470,7 @@ class TestCCXTExchange:
stake_amount=100, stake_amount=100,
leverage=5, leverage=5,
wallet_balance=100, wallet_balance=100,
open_trades=[],
) )
assert isinstance(liquidation_price, float) assert isinstance(liquidation_price, float)
assert liquidation_price >= 0.0 assert liquidation_price >= 0.0
@@ -0,0 +1,57 @@
from unittest.mock import MagicMock
import pytest
from freqtrade.enums.marginmode import MarginMode
from freqtrade.leverage.liquidation_price import update_liquidation_prices
@pytest.mark.parametrize("dry_run", [False, True])
@pytest.mark.parametrize("margin_mode", [MarginMode.CROSS, MarginMode.ISOLATED])
def test_update_liquidation_prices(mocker, margin_mode, dry_run):
# Heavily mocked test - Only testing the logic of the function
# update liquidation price for trade in isolated mode
# update liquidation price for all trades in cross mode
exchange = MagicMock()
exchange.margin_mode = margin_mode
wallets = MagicMock()
trade_mock = MagicMock()
mocker.patch("freqtrade.persistence.Trade.get_open_trades", return_value=[trade_mock])
update_liquidation_prices(
trade=trade_mock,
exchange=exchange,
wallets=wallets,
stake_currency="USDT",
dry_run=dry_run,
)
assert trade_mock.set_liquidation_price.call_count == 1
assert wallets.get_total.call_count == (
0 if margin_mode == MarginMode.ISOLATED or not dry_run else 1
)
# Test with multiple trades
trade_mock.reset_mock()
trade_mock_2 = MagicMock()
mocker.patch(
"freqtrade.persistence.Trade.get_open_trades", return_value=[trade_mock, trade_mock_2]
)
update_liquidation_prices(
trade=trade_mock,
exchange=exchange,
wallets=wallets,
stake_currency="USDT",
dry_run=dry_run,
)
# Trade2 is only updated in cross mode
assert trade_mock_2.set_liquidation_price.call_count == (
1 if margin_mode == MarginMode.CROSS else 0
)
assert trade_mock.set_liquidation_price.call_count == 1
assert wallets.call_count == 0 if not dry_run else 1
+2 -2
View File
@@ -1269,7 +1269,7 @@ def test_api_mix_tag(botclient, fee):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"is_short,current_rate,open_trade_value", "is_short,current_rate,open_trade_value",
[(True, 1.098e-05, 15.0911775), (False, 1.099e-05, 15.1668225)], [(True, 1.098e-05, 6.134625), (False, 1.099e-05, 6.165375)],
) )
def test_api_status( def test_api_status(
botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value
@@ -1294,7 +1294,7 @@ def test_api_status(
assert_response(rc) assert_response(rc)
assert len(rc.json()) == 4 assert len(rc.json()) == 4
assert rc.json()[0] == { assert rc.json()[0] == {
"amount": 123.0, "amount": 50.0,
"amount_requested": 123.0, "amount_requested": 123.0,
"close_date": None, "close_date": None,
"close_timestamp": None, "close_timestamp": None,
+7 -6
View File
@@ -362,7 +362,8 @@ def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
assert len(freqtrade.wallets._wallets) == 5 assert len(freqtrade.wallets._wallets) == 5
assert len(freqtrade.wallets._positions) == 0 assert len(freqtrade.wallets._positions) == 0
bal = freqtrade.wallets.get_all_balances() bal = freqtrade.wallets.get_all_balances()
assert bal["NEO"].total == 10 # NEO trade is not filled yet.
assert bal["NEO"].total == 0
assert bal["XRP"].total == 10 assert bal["XRP"].total == 10
assert bal["LTC"].total == 2 assert bal["LTC"].total == 2
usdt_bal = bal["USDT"] usdt_bal = bal["USDT"]
@@ -410,11 +411,11 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee):
def test_check_exit_amount(mocker, default_conf, fee): def test_check_exit_amount(mocker, default_conf, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
update_mock = mocker.patch("freqtrade.wallets.Wallets.update") update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50.0)
create_mock_trades(fee, is_short=None) create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.amount == 123 assert trade.amount == 50.0
assert freqtrade.wallets.check_exit_amount(trade) is True assert freqtrade.wallets.check_exit_amount(trade) is True
assert update_mock.call_count == 0 assert update_mock.call_count == 0
@@ -423,7 +424,7 @@ def test_check_exit_amount(mocker, default_conf, fee):
update_mock.reset_mock() update_mock.reset_mock()
# Reduce returned amount to below the trade amount - which should # Reduce returned amount to below the trade amount - which should
# trigger a wallet update and return False, triggering "order refinding" # trigger a wallet update and return False, triggering "order refinding"
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100) total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=40)
assert freqtrade.wallets.check_exit_amount(trade) is False assert freqtrade.wallets.check_exit_amount(trade) is False
assert update_mock.call_count == 1 assert update_mock.call_count == 1
assert total_mock.call_count == 2 assert total_mock.call_count == 2
@@ -433,12 +434,12 @@ def test_check_exit_amount_futures(mocker, default_conf, fee):
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50)
create_mock_trades(fee, is_short=None) create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first() trade = Trade.session.scalars(select(Trade)).first()
trade.trading_mode = "futures" trade.trading_mode = "futures"
assert trade.amount == 123 assert trade.amount == 50
assert freqtrade.wallets.check_exit_amount(trade) is True assert freqtrade.wallets.check_exit_amount(trade) is True
assert total_mock.call_count == 0 assert total_mock.call_count == 0