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

View File

@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
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:
- uses: actions/checkout@v4
@@ -72,7 +72,7 @@ jobs:
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- 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:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
@@ -139,10 +139,7 @@ jobs:
strategy:
matrix:
os: [ "macos-12", "macos-13", "macos-14" ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
exclude:
- os: "macos-14"
python-version: "3.9"
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
@@ -263,7 +260,7 @@ jobs:
strategy:
matrix:
os: [ windows-latest ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4

View File

@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.6.7'
rev: 'v0.6.8'
hooks:
- id: ruff
- id: ruff-format

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
ENV LANG C.UTF-8

View File

@@ -61,7 +61,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
## 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] **Dry-run**: Run the bot without paying money.
- [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
- [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/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [TA-Lib](https://ta-lib.github.io/ta-lib-python/)

View File

@@ -85,7 +85,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
Alternatively
- Python 3.9+
- Python 3.10+
- pip (pip3)
- git
- TA-Lib

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).
!!! 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.
!!! Warning "Up-to-date clock"
@@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and
### 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/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [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.
!!! 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"
#### Install necessary dependencies
@@ -69,7 +69,7 @@ OS Specific steps are listed first, the common section below is necessary for al
=== "RaspberryPi/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.
@@ -169,7 +169,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr
** --install **
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`
* Setup your virtualenv under `.venv/`

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.
!!! 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

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.
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
@@ -42,7 +42,7 @@ cd freqtrade
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.
Other versions must be downloaded from the above link.

View File

@@ -3,7 +3,7 @@
__main__.py for Freqtrade
To launch Freqtrade as a module
> python -m freqtrade (with Python >= 3.9)
> python -m freqtrade (with Python >= 3.10)
"""
from freqtrade import main

View File

@@ -1,5 +1,4 @@
import logging
import sys
import time
from pathlib import Path
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.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)
strategy_objs = StrategyResolver.search_all_objects(

View File

@@ -144,6 +144,29 @@ class Binance(Exchange):
"""
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(
self,
pair: str,
@@ -153,8 +176,7 @@ class Binance(Exchange):
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
open_trades: list,
) -> Optional[float]:
"""
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
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of open trades in the same wallet
# * Only required for Cross
:param mm_ex_1: (TMM)
@@ -180,15 +203,41 @@ class Binance(Exchange):
:param upnl_ex_1: (UPNL)
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0
:param other
"""
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
cross_vars: float = 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position
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:
raise OperationalException(
"Parameter maintenance_amt is required by Binance.liquidation_price"

File diff suppressed because it is too large Load Diff

View File

@@ -147,8 +147,7 @@ class Bybit(Exchange):
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
open_trades: list,
) -> Optional[float]:
"""
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
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of other open trades in the same wallet
"""
market = self.markets[pair]

View File

@@ -3532,8 +3532,7 @@ class Exchange:
stake_amount: float,
leverage: float,
wallet_balance: float,
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
open_trades: Optional[list] = None,
) -> Optional[float]:
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
@@ -3555,8 +3554,7 @@ class Exchange:
leverage=leverage,
stake_amount=stake_amount,
wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1,
open_trades=open_trades or [],
)
else:
positions = self.fetch_positions(pair)
@@ -3582,8 +3580,7 @@ class Exchange:
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
open_trades: list,
) -> Optional[float]:
"""
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
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
# * Not required by Gate or OKX
:param mm_ex_1:
:param upnl_ex_1:
:param open_trades: List of other open trades in the same wallet
"""
market = self.markets[pair]

View File

@@ -43,6 +43,7 @@ from freqtrade.exchange import (
timeframe_to_next_date,
timeframe_to_seconds,
)
from freqtrade.leverage.liquidation_price import update_liquidation_prices
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin
from freqtrade.persistence import Order, PairLocks, Trade, init_db
@@ -241,6 +242,7 @@ class FreqtradeBot(LoggingMixin):
# Only update open orders on startup
# This will update the database after the initial migration
self.startup_update_open_orders()
self.update_all_liquidation_prices()
self.update_funding_fees()
def process(self) -> None:
@@ -357,6 +359,16 @@ class FreqtradeBot(LoggingMixin):
open_trades = Trade.get_open_trade_count()
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:
if self.trading_mode == TradingMode.FUTURES:
trades: List[Trade] = Trade.get_open_trades()
@@ -2233,20 +2245,13 @@ class FreqtradeBot(LoggingMixin):
# Must also run for partial exits
# TODO: Margin will need to use interest_rate as well.
# interest_rate = self.exchange.get_interest_rate()
try:
trade.set_liquidation_price(
self.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,
)
)
except DependencyException:
logger.warning("Unable to calculate liquidation price")
update_liquidation_prices(
trade,
exchange=self.exchange,
wallets=self.wallets,
stake_currency=self.config["stake_currency"],
dry_run=self.config["dry_run"],
)
if self.strategy.use_custom_stoploss:
current_rate = self.exchange.get_rate(
trade.pair, side="exit", is_short=trade.is_short, refresh=True

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")

View File

@@ -10,8 +10,8 @@ from typing import Any, List, Optional
# check min. python version
if sys.version_info < (3, 9): # pragma: no cover
sys.exit("Freqtrade requires Python version >= 3.9")
if sys.version_info < (3, 10): # pragma: no cover
sys.exit("Freqtrade requires Python version >= 3.10")
from freqtrade import __version__
from freqtrade.commands import Arguments

View File

@@ -26,6 +26,7 @@ from freqtrade.enums import (
CandleType,
ExitCheckTuple,
ExitType,
MarginMode,
RunMode,
TradingMode,
)
@@ -37,6 +38,7 @@ from freqtrade.exchange import (
)
from freqtrade.exchange.exchange import Exchange
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.optimize.backtest_caching import get_strategy_run_id
from freqtrade.optimize.bt_progress import BTProgress
@@ -206,6 +208,7 @@ class Backtesting:
self.required_startup = self.dataprovider.get_required_startup(self.timeframe)
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.
self._can_short = self.trading_mode != TradingMode.SPOT
self._position_stacking: bool = self.config.get("position_stacking", False)
@@ -698,21 +701,20 @@ class Backtesting:
current_time=current_date,
)
if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount):
# trade is still open
trade.set_liquidation_price(
self.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,
)
if self.margin_mode == MarginMode.CROSS or not (
order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount
):
# trade is still open or we are in cross margin mode and
# must update all liquidation prices
update_liquidation_prices(
trade,
exchange=self.exchange,
wallets=self.wallets,
stake_currency=self.config["stake_currency"],
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)
# pass
return True
return False

View File

@@ -760,7 +760,7 @@ class LocalTrade:
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price
"""
if not liquidation_price:
if liquidation_price is None:
return
self.liquidation_price = liquidation_price

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)
async def index_html(rest_of_path: str):
"""
@@ -56,7 +46,7 @@ async def index_html(rest_of_path: str):
if filename.suffix == ".js":
# Force text/javascript for .js files - Circumvent faulty system configuration
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)
index_file = uibase / "index.html"

View File

@@ -13,14 +13,13 @@ authors = [
description = "Freqtrade - Client scripts"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {text = "GPLv3"}
# license = "GPLv3"
classifiers = [
"Environment :: Console",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",

View File

@@ -13,14 +13,12 @@ authors = [
description = "Freqtrade - Crypto Trading Bot"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {text = "GPLv3"}
# license = "GPLv3"
classifiers = [
"Environment :: Console",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
@@ -125,7 +123,6 @@ extend-exclude = [".env", ".venv"]
target-version = "py38"
[tool.ruff.lint]
# Exclude UP036 as it's causing the "exit if < 3.9" to fail.
extend-select = [
"C90", # mccabe
"B", # bugbear

View File

@@ -2,8 +2,7 @@
-r requirements.txt
# Required for hyperopt
scipy==1.14.1; python_version >= "3.10"
scipy==1.13.1; python_version < "3.10"
scipy==1.14.1
scikit-learn==1.5.2
ft-scikit-optimize==0.9.2
filelock==3.16.1

View File

@@ -22,9 +22,7 @@ technical==1.4.4
tabulate==0.9.0
pycoingecko==3.1.0
jinja2==3.1.4
# Tables 3.10 dropped support for Python 3.9
tables==3.9.1; python_version < "3.10"
tables==3.10.1; python_version >= "3.10"
tables==3.10.1
joblib==1.4.2
rich==13.8.1
pyarrow==17.0.0; platform_machine != 'armv7l'

View File

@@ -153,16 +153,13 @@ function Find-PythonExecutable {
"python3.12",
"python3.11",
"python3.10",
"python3.9",
"python3",
"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\Python310\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe",
"C:\Python312\python.exe",
"C:\Python311\python.exe",
"C:\Python310\python.exe",
"C:\Python39\python.exe"
"C:\Python310\python.exe"
)
@@ -178,10 +175,10 @@ function Main {
"Starting the operations..." | 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
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
}

View File

@@ -25,7 +25,7 @@ function check_installed_python() {
exit 2
fi
for v in 12 11 10 9
for v in 12 11 10
do
PYTHON="python3.${v}"
which $PYTHON
@@ -36,7 +36,7 @@ function check_installed_python() {
fi
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
}
@@ -166,7 +166,7 @@ function install_macos() {
#Gets number after decimal in python version
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
fi
}
@@ -277,7 +277,7 @@ function install() {
install_redhat
else
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."
sleep 10
fi
@@ -304,7 +304,7 @@ function help() {
echo " -p,--plot Install dependencies for Plotting scripts."
}
# Verify if 3.9+ is installed
# Verify if 3.10+ is installed
check_installed_python
case $* in

View File

@@ -38,7 +38,7 @@ def mock_trade_1(fee, is_short: bool):
trade = Trade(
pair="ETH/BTC",
stake_amount=0.001,
amount=123.0,
amount=50.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
@@ -201,7 +201,7 @@ def mock_trade_4(fee, is_short: bool):
trade = Trade(
pair="ETC/BTC",
stake_amount=0.001,
amount=123.0,
amount=0.0,
amount_requested=124.0,
fee_open=fee.return_value,
fee_close=fee.return_value,

View File

@@ -224,7 +224,7 @@ def mock_trade_usdt_4(fee, is_short: bool):
trade = Trade(
pair="NEO/USDT",
stake_amount=20.0,
amount=10.0,
amount=0.0,
amount_requested=10.01,
fee_open=fee.return_value,
fee_close=fee.return_value,

View File

@@ -7,6 +7,7 @@ import pytest
from freqtrade.enums import CandleType, MarginMode, TradingMode
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.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(
"is_short, trading_mode, margin_mode, wallet_balance, "
"mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, "
"pair, is_short, trading_mode, margin_mode, wallet_balance, "
"maintenance_amt, amount, open_rate, open_trades,"
"mm_ratio, expected",
[
(
"ETH/USDT:USDT",
False,
"futures",
"isolated",
1535443.01,
0.0,
0.0,
135365.00,
3683.979,
1456.84,
[],
0.10,
1114.78,
),
(
"ETH/USDT:USDT",
False,
"futures",
"isolated",
1535443.01,
0.0,
0.0,
16300.000,
109.488,
32481.980,
[],
0.025,
18778.73,
),
(
"ETH/USDT:USDT",
False,
"futures",
"cross",
1535443.01,
71200.81144,
-56354.57,
135365.00,
3683.979,
1456.84,
3683.979, # amount
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,
1153.26,
),
(
"BTC/USDT:USDT",
False,
"futures",
"cross",
1535443.01,
356512.508,
-448192.89,
16300.000,
109.488,
32481.980,
16300.0,
109.488, # amount
32481.980, # 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.025,
26316.89,
),
@@ -232,15 +275,15 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
def test_liquidation_price_binance(
mocker,
default_conf,
open_rate,
pair,
is_short,
trading_mode,
margin_mode,
wallet_balance,
mm_ex_1,
upnl_ex_1,
maintenance_amt,
amount,
open_rate,
open_trades,
mm_ratio,
expected,
):
@@ -248,20 +291,48 @@ def test_liquidation_price_binance(
default_conf["margin_mode"] = margin_mode
default_conf["liquidation_buffer"] = 0.0
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 (
pytest.approx(
round(
exchange.get_liquidation_price(
pair="DOGE/USDT",
pair=pair,
open_rate=open_rate,
is_short=is_short,
wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1,
amount=amount,
stake_amount=open_rate * amount,
leverage=5,
open_trades=open_trade_objects,
),
2,
)

View File

@@ -5524,8 +5524,6 @@ def test_liquidation_price_is_none(
stake_amount=open_rate * 71200.81144,
leverage=5,
wallet_balance=-56354.57,
mm_ex_1=0.10,
upnl_ex_1=0.0,
)
is None
)
@@ -6011,6 +6009,7 @@ def test_get_liquidation_price1(mocker, default_conf):
stake_amount=18.884 * 0.8,
leverage=leverage,
wallet_balance=18.884 * 0.8,
open_trades=[],
)
@@ -6141,6 +6140,7 @@ def test_get_liquidation_price(
wallet_balance=amount * open_rate / leverage,
leverage=leverage,
is_short=is_short,
open_trades=[],
)
if expected_liq is None:
assert liq is None

View File

@@ -457,6 +457,7 @@ class TestCCXTExchange:
stake_amount=100,
leverage=5,
wallet_balance=100,
open_trades=[],
)
assert isinstance(liquidation_price, float)
assert liquidation_price >= 0.0
@@ -469,6 +470,7 @@ class TestCCXTExchange:
stake_amount=100,
leverage=5,
wallet_balance=100,
open_trades=[],
)
assert isinstance(liquidation_price, float)
assert liquidation_price >= 0.0

View File

@@ -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

View File

@@ -1269,7 +1269,7 @@ def test_api_mix_tag(botclient, fee):
@pytest.mark.parametrize(
"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(
botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value
@@ -1294,7 +1294,7 @@ def test_api_status(
assert_response(rc)
assert len(rc.json()) == 4
assert rc.json()[0] == {
"amount": 123.0,
"amount": 50.0,
"amount_requested": 123.0,
"close_date": None,
"close_timestamp": None,

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._positions) == 0
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["LTC"].total == 2
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):
freqtrade = get_patched_freqtradebot(mocker, default_conf)
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)
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 update_mock.call_count == 0
@@ -423,7 +424,7 @@ def test_check_exit_amount(mocker, default_conf, fee):
update_mock.reset_mock()
# Reduce returned amount to below the trade amount - which should
# 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 update_mock.call_count == 1
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["margin_mode"] = "isolated"
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)
trade = Trade.session.scalars(select(Trade)).first()
trade.trading_mode = "futures"
assert trade.amount == 123
assert trade.amount == 50
assert freqtrade.wallets.check_exit_amount(trade) is True
assert total_mock.call_count == 0