mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-15 20:31:43 +00:00
Merge branch 'develop' into fix/remove_deprecation_warning
This commit is contained in:
@@ -27,8 +27,9 @@ hesitate to read the source code and understand the mechanism of this bot.
|
|||||||
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
|
||||||
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||||
|
- [X] [Bitget](https://www.bitget.com/)
|
||||||
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [HTX](https://www.htx.com/)
|
- [X] [HTX](https://www.htx.com/)
|
||||||
@@ -41,6 +42,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
|
|||||||
### Supported Futures Exchanges (experimental)
|
### Supported Futures Exchanges (experimental)
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
|
- [X] [Bitget](https://www.bitget.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [OKX](https://okx.com/)
|
- [X] [OKX](https://okx.com/)
|
||||||
|
|||||||
@@ -298,7 +298,14 @@ Without these permissions, the bot will not start correctly and show errors like
|
|||||||
|
|
||||||
Bybit supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
|
Bybit supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
|
||||||
|
|
||||||
Futures trading on bybit is currently supported for isolated futures mode.
|
!!! Warning "Unified accounts"
|
||||||
|
Freqtrade assumes accounts to be dedicated to the bot.
|
||||||
|
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
|
||||||
|
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
|
||||||
|
|
||||||
|
### Bybit Futures
|
||||||
|
|
||||||
|
Futures trading on bybit is supported for isolated futures mode.
|
||||||
|
|
||||||
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
|
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
|
||||||
|
|
||||||
@@ -312,10 +319,6 @@ API Keys for live futures trading must have the following permissions:
|
|||||||
|
|
||||||
We do strongly recommend to limit all API keys to the IP you're going to use it from.
|
We do strongly recommend to limit all API keys to the IP you're going to use it from.
|
||||||
|
|
||||||
!!! Warning "Unified accounts"
|
|
||||||
Freqtrade assumes accounts to be dedicated to the bot.
|
|
||||||
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
|
|
||||||
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
|
|
||||||
|
|
||||||
## Bitmart
|
## Bitmart
|
||||||
|
|
||||||
@@ -355,6 +358,12 @@ Bitget supports [time_in_force](configuration.md#understand-order_time_in_force)
|
|||||||
Bitget supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
Bitget supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||||
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used.
|
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used.
|
||||||
|
|
||||||
|
### Bitget Futures
|
||||||
|
|
||||||
|
Futures trading on bitget is supported for isolated futures mode.
|
||||||
|
|
||||||
|
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
|
||||||
|
|
||||||
## Hyperliquid
|
## Hyperliquid
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "Stoploss on Exchange"
|
||||||
@@ -478,3 +487,5 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
|
|||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Please make sure to fully understand the impacts of these settings before modifying them.
|
Please make sure to fully understand the impacts of these settings before modifying them.
|
||||||
|
Using `_ft_has_params` overrides may lead to unexpected behavior, and may even break your bot.
|
||||||
|
We will not be able to provide support for issues caused by custom settings in `_ft_has_params`.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
| [Binance](exchanges.md#binance) | futures | isolated, cross | market, limit |
|
| [Binance](exchanges.md#binance) | futures | isolated, cross | market, limit |
|
||||||
| [Bingx](exchanges.md#bingx) | spot | | market, limit |
|
| [Bingx](exchanges.md#bingx) | spot | | market, limit |
|
||||||
| [Bitmart](exchanges.md#bitmart) | spot | | ❌ (not supported) |
|
| [Bitmart](exchanges.md#bitmart) | spot | | ❌ (not supported) |
|
||||||
|
| [Bitget](exchanges.md#bitget) | spot | | market, limit |
|
||||||
|
| [Bitget](exchanges.md#bitget) | futures | isolated | market, limit |
|
||||||
| [Bybit](exchanges.md#bybit) | spot | | ❌ (not supported) |
|
| [Bybit](exchanges.md#bybit) | spot | | ❌ (not supported) |
|
||||||
| [Bybit](exchanges.md#bybit) | futures | isolated | market, limit |
|
| [Bybit](exchanges.md#bybit) | futures | isolated | market, limit |
|
||||||
| [Gate.io](exchanges.md#gateio) | spot | | limit |
|
| [Gate.io](exchanges.md#gateio) | spot | | limit |
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
|
|||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||||
|
- [X] [Bitget](https://www.bitget.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
@@ -52,6 +53,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
|
|||||||
### Supported Futures Exchanges (experimental)
|
### Supported Futures Exchanges (experimental)
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
|
- [X] [Bitget](https://www.bitget.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
markdown==3.9
|
markdown==3.9
|
||||||
mkdocs==1.6.1
|
mkdocs==1.6.1
|
||||||
mkdocs-material==9.6.21
|
mkdocs-material==9.6.22
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==10.16.1
|
pymdown-extensions==10.16.1
|
||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from datetime import timedelta
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.constants import BuySell
|
||||||
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.exceptions import (
|
from freqtrade.exceptions import (
|
||||||
DDosProtection,
|
DDosProtection,
|
||||||
OperationalException,
|
OperationalException,
|
||||||
@@ -20,27 +21,30 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Bitget(Exchange):
|
class Bitget(Exchange):
|
||||||
"""
|
"""Bitget exchange class.
|
||||||
Bitget exchange class. Contains adjustments needed for Freqtrade to work
|
Contains adjustments needed for Freqtrade to work with this exchange.
|
||||||
with this exchange.
|
|
||||||
|
|
||||||
Please note that this exchange is not included in the list of exchanges
|
|
||||||
officially supported by the Freqtrade development team. So some features
|
|
||||||
may still not work as expected.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ft_has: FtHas = {
|
_ft_has: FtHas = {
|
||||||
"stoploss_on_exchange": True,
|
"stoploss_on_exchange": True,
|
||||||
"stop_price_param": "stopPrice",
|
"stop_price_param": "stopPrice",
|
||||||
"stop_price_prop": "stopPrice",
|
"stop_price_prop": "stopPrice",
|
||||||
|
"stoploss_blocks_assets": False, # Stoploss orders do not block assets
|
||||||
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
||||||
"ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones.
|
"ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones.
|
||||||
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
|
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
|
||||||
}
|
}
|
||||||
_ft_has_futures: FtHas = {
|
_ft_has_futures: FtHas = {
|
||||||
"mark_ohlcv_timeframe": "4h",
|
"mark_ohlcv_timeframe": "4h",
|
||||||
|
"funding_fee_candle_limit": 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||||
|
(TradingMode.SPOT, MarginMode.NONE),
|
||||||
|
(TradingMode.FUTURES, MarginMode.ISOLATED),
|
||||||
|
# (TradingMode.FUTURES, MarginMode.CROSS),
|
||||||
|
]
|
||||||
|
|
||||||
def ohlcv_candle_limit(
|
def ohlcv_candle_limit(
|
||||||
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
|
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
|
||||||
) -> int:
|
) -> int:
|
||||||
@@ -126,3 +130,109 @@ class Bitget(Exchange):
|
|||||||
|
|
||||||
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
|
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
|
||||||
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
|
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
|
||||||
|
|
||||||
|
@retrier
|
||||||
|
def additional_exchange_init(self) -> None:
|
||||||
|
"""
|
||||||
|
Additional exchange initialization logic.
|
||||||
|
.api will be available at this point.
|
||||||
|
Must be overridden in child methods if required.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self._config["dry_run"]:
|
||||||
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
|
position_mode = self._api.set_position_mode(False)
|
||||||
|
self._log_exchange_response("set_position_mode", position_mode)
|
||||||
|
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 _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
|
||||||
|
if self.trading_mode != TradingMode.SPOT:
|
||||||
|
# Explicitly setting margin_mode is not necessary as marginMode can be set per order.
|
||||||
|
# self.set_margin_mode(pair, self.margin_mode, accept_fail)
|
||||||
|
self._set_leverage(leverage, pair, accept_fail)
|
||||||
|
|
||||||
|
def _get_params(
|
||||||
|
self,
|
||||||
|
side: BuySell,
|
||||||
|
ordertype: str,
|
||||||
|
leverage: float,
|
||||||
|
reduceOnly: bool,
|
||||||
|
time_in_force: str = "GTC",
|
||||||
|
) -> dict:
|
||||||
|
params = super()._get_params(
|
||||||
|
side=side,
|
||||||
|
ordertype=ordertype,
|
||||||
|
leverage=leverage,
|
||||||
|
reduceOnly=reduceOnly,
|
||||||
|
time_in_force=time_in_force,
|
||||||
|
)
|
||||||
|
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
|
||||||
|
params["marginMode"] = self.margin_mode.value.lower()
|
||||||
|
return params
|
||||||
|
|
||||||
|
def dry_run_liquidation_price(
|
||||||
|
self,
|
||||||
|
pair: str,
|
||||||
|
open_rate: float,
|
||||||
|
is_short: bool,
|
||||||
|
amount: float,
|
||||||
|
stake_amount: float,
|
||||||
|
leverage: float,
|
||||||
|
wallet_balance: float,
|
||||||
|
open_trades: list,
|
||||||
|
) -> float | None:
|
||||||
|
"""
|
||||||
|
Important: Must be fetching data from cached values as this is used by backtesting!
|
||||||
|
|
||||||
|
|
||||||
|
https://www.bitget.com/support/articles/12560603808759
|
||||||
|
MMR: Maintenance margin rate of the trading pair.
|
||||||
|
|
||||||
|
CoinMainIndexPrice: The index price for Coin-M futures. For USDT-M futures,
|
||||||
|
the index price is: 1.
|
||||||
|
|
||||||
|
TakerFeeRatio: The fee rate applied when placing taker orders.
|
||||||
|
|
||||||
|
Position direction: The current position direction of the trading pair.
|
||||||
|
1 indicates a long position, and -1 indicates a short position.
|
||||||
|
|
||||||
|
Formula:
|
||||||
|
|
||||||
|
Estimated liquidation price = [
|
||||||
|
position margin - position size x average entry price x position direction
|
||||||
|
] ÷ [position size x (MMR + TakerFeeRatio - position direction)]
|
||||||
|
|
||||||
|
:param pair: Pair to calculate liquidation price for
|
||||||
|
:param open_rate: Entry price of position
|
||||||
|
:param is_short: True if the trade is a short, false otherwise
|
||||||
|
:param amount: Absolute value of position size incl. leverage (in base currency)
|
||||||
|
:param stake_amount: Stake amount - Collateral in settle currency.
|
||||||
|
:param leverage: Leverage used for this position.
|
||||||
|
: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]
|
||||||
|
taker_fee_rate = market["taker"] or self._api.describe().get("fees", {}).get(
|
||||||
|
"trading", {}
|
||||||
|
).get("taker", 0.001)
|
||||||
|
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount)
|
||||||
|
|
||||||
|
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
|
||||||
|
position_direction = -1 if is_short else 1
|
||||||
|
|
||||||
|
return (wallet_balance - (amount * open_rate * position_direction)) / (
|
||||||
|
amount * (mm_ratio + taker_fee_rate - position_direction)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise OperationalException(
|
||||||
|
"Freqtrade currently only supports isolated futures for bitget"
|
||||||
|
)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ SUPPORTED_EXCHANGES = [
|
|||||||
"binance",
|
"binance",
|
||||||
"bingx",
|
"bingx",
|
||||||
"bitmart",
|
"bitmart",
|
||||||
|
"bitget",
|
||||||
"bybit",
|
"bybit",
|
||||||
"gate",
|
"gate",
|
||||||
"htx",
|
"htx",
|
||||||
|
|||||||
@@ -85,17 +85,17 @@ hyperopt = [
|
|||||||
freqai = [
|
freqai = [
|
||||||
"scikit-learn",
|
"scikit-learn",
|
||||||
"joblib",
|
"joblib",
|
||||||
'catboost; platform_machine != "arm"',
|
"catboost; platform_machine != 'arm'",
|
||||||
"lightgbm",
|
"lightgbm",
|
||||||
"xgboost",
|
"xgboost",
|
||||||
"tensorboard",
|
"tensorboard",
|
||||||
"datasieve>=0.1.5",
|
"datasieve>=0.1.5",
|
||||||
]
|
]
|
||||||
freqai_rl = [
|
freqai_rl = [
|
||||||
"torch",
|
"torch; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||||||
"gymnasium",
|
"gymnasium",
|
||||||
"stable-baselines3",
|
"stable-baselines3; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||||||
"sb3-contrib",
|
"sb3-contrib; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||||||
"tqdm",
|
"tqdm",
|
||||||
]
|
]
|
||||||
develop = [
|
develop = [
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
-r requirements-freqai.txt
|
-r requirements-freqai.txt
|
||||||
|
|
||||||
# Required for freqai-rl
|
# Required for freqai-rl
|
||||||
torch==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
torch==2.9.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||||
gymnasium==0.29.1
|
gymnasium==1.2.1
|
||||||
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
|
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
|
||||||
stable_baselines3==2.7.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
stable_baselines3==2.7.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||||
sb3_contrib>=2.2.1
|
sb3_contrib>=2.2.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||||
# Progress bar for stable-baselines3 and sb3-contrib
|
# Progress bar for stable-baselines3 and sb3-contrib
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
numpy==2.3.3
|
numpy==2.3.4
|
||||||
pandas==2.3.3
|
pandas==2.3.3
|
||||||
bottleneck==1.6.0
|
bottleneck==1.6.0
|
||||||
numexpr==2.13.1
|
numexpr==2.14.1
|
||||||
# Indicator libraries
|
# Indicator libraries
|
||||||
ft-pandas-ta==0.3.16
|
ft-pandas-ta==0.3.16
|
||||||
ta-lib==0.6.7
|
ta-lib==0.6.7
|
||||||
technical==1.5.3
|
technical==1.5.3
|
||||||
|
|
||||||
ccxt==4.5.11
|
ccxt==4.5.11
|
||||||
cryptography==46.0.2
|
cryptography==46.0.3
|
||||||
aiohttp==3.13.0
|
aiohttp==3.13.0
|
||||||
SQLAlchemy==2.0.44
|
SQLAlchemy==2.0.44
|
||||||
python-telegram-bot==22.5
|
python-telegram-bot==22.5
|
||||||
# can't be hard-pinned due to telegram-bot pinning httpx with ~
|
# can't be hard-pinned due to telegram-bot pinning httpx with ~
|
||||||
httpx>=0.24.1
|
httpx>=0.24.1
|
||||||
humanize==4.13.0
|
humanize==4.14.0
|
||||||
cachetools==6.2.1
|
cachetools==6.2.1
|
||||||
requests==2.32.5
|
requests==2.32.5
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
@@ -39,7 +39,7 @@ sdnotify==0.3.2
|
|||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.119.0
|
fastapi==0.119.0
|
||||||
pydantic==2.12.0
|
pydantic==2.12.2
|
||||||
uvicorn==0.37.0
|
uvicorn==0.37.0
|
||||||
pyjwt==2.10.1
|
pyjwt==2.10.1
|
||||||
aiofiles==25.1.0
|
aiofiles==25.1.0
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.exceptions import RetryableOrderError
|
from freqtrade.exceptions import OperationalException, RetryableOrderError
|
||||||
from freqtrade.exchange.common import API_RETRY_COUNT
|
from freqtrade.exchange.common import API_RETRY_COUNT
|
||||||
from freqtrade.util import dt_now, dt_ts
|
from freqtrade.util import dt_now, dt_ts
|
||||||
from tests.conftest import EXMS, get_patched_exchange
|
from tests.conftest import EXMS, get_patched_exchange
|
||||||
@@ -120,3 +120,76 @@ def test_bitget_ohlcv_candle_limit(mocker, default_conf_usdt):
|
|||||||
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length
|
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length
|
||||||
assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == length
|
assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == length
|
||||||
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200
|
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_additional_exchange_init_bitget(default_conf, mocker):
|
||||||
|
default_conf["dry_run"] = False
|
||||||
|
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||||
|
default_conf["margin_mode"] = MarginMode.ISOLATED
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.set_position_mode = MagicMock(return_value={})
|
||||||
|
|
||||||
|
get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
|
||||||
|
assert api_mock.set_position_mode.call_count == 1
|
||||||
|
|
||||||
|
ccxt_exceptionhandlers(
|
||||||
|
mocker, default_conf, api_mock, "bitget", "additional_exchange_init", "set_position_mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dry_run_liquidation_price_cross_bitget(default_conf, mocker):
|
||||||
|
default_conf["dry_run"] = True
|
||||||
|
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||||
|
default_conf["margin_mode"] = MarginMode.CROSS
|
||||||
|
api_mock = MagicMock()
|
||||||
|
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", MagicMock(return_value=(0.005, 0.0)))
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
OperationalException, match="Freqtrade currently only supports isolated futures for bitget"
|
||||||
|
):
|
||||||
|
exchange.dry_run_liquidation_price(
|
||||||
|
"ETH/USDT:USDT",
|
||||||
|
100_000,
|
||||||
|
False,
|
||||||
|
0.1,
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test__lev_prep_bitget(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.set_margin_mode = MagicMock()
|
||||||
|
api_mock.set_leverage = MagicMock()
|
||||||
|
type(api_mock).has = PropertyMock(return_value={"setMarginMode": True, "setLeverage": True})
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
|
||||||
|
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
|
||||||
|
|
||||||
|
assert api_mock.set_margin_mode.call_count == 0
|
||||||
|
assert api_mock.set_leverage.call_count == 0
|
||||||
|
|
||||||
|
# test in futures mode
|
||||||
|
api_mock.set_margin_mode.reset_mock()
|
||||||
|
api_mock.set_leverage.reset_mock()
|
||||||
|
default_conf["dry_run"] = False
|
||||||
|
|
||||||
|
default_conf["trading_mode"] = "futures"
|
||||||
|
default_conf["margin_mode"] = "isolated"
|
||||||
|
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
|
||||||
|
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
|
||||||
|
|
||||||
|
assert api_mock.set_margin_mode.call_count == 0
|
||||||
|
assert api_mock.set_leverage.call_count == 1
|
||||||
|
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=3.2)
|
||||||
|
|
||||||
|
api_mock.reset_mock()
|
||||||
|
|
||||||
|
exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell")
|
||||||
|
|
||||||
|
assert api_mock.set_margin_mode.call_count == 0
|
||||||
|
assert api_mock.set_leverage.call_count == 1
|
||||||
|
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=19.99)
|
||||||
|
|||||||
@@ -5942,29 +5942,32 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
|
|||||||
assert exchange.get_max_leverage("TIA/USDT:USDT", 130.008) == 40
|
assert exchange.get_max_leverage("TIA/USDT:USDT", 130.008) == 40
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", ["binance", "kraken", "gate", "okx", "bybit"])
|
@pytest.mark.parametrize(
|
||||||
def test__get_params(mocker, default_conf, exchange_name):
|
"exchange_name, add_params_spot, add_params_futures",
|
||||||
|
[
|
||||||
|
("binance", {}, {}),
|
||||||
|
("kraken", {}, {"leverage": 3.0}),
|
||||||
|
("gate", {}, {}),
|
||||||
|
("okx", {}, {"tdMode": "isolated", "posSide": "net"}),
|
||||||
|
("bybit", {}, {"position_idx": 0}),
|
||||||
|
("bitget", {}, {"marginMode": "isolated"}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test__get_params(mocker, default_conf, exchange_name, add_params_spot, add_params_futures):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
|
||||||
exchange._params = {"test": True}
|
exchange._params = {"test": True}
|
||||||
|
|
||||||
params1 = {"test": True}
|
params1 = {"test": True}
|
||||||
params2 = {
|
params1.update(add_params_spot)
|
||||||
|
|
||||||
|
params_fut = {
|
||||||
"test": True,
|
"test": True,
|
||||||
"timeInForce": "IOC",
|
"timeInForce": "IOC",
|
||||||
"reduceOnly": True,
|
"reduceOnly": True,
|
||||||
}
|
}
|
||||||
|
params_fut.update(add_params_futures)
|
||||||
if exchange_name == "kraken":
|
|
||||||
params2["leverage"] = 3.0
|
|
||||||
|
|
||||||
if exchange_name == "okx":
|
|
||||||
params2["tdMode"] = "isolated"
|
|
||||||
params2["posSide"] = "net"
|
|
||||||
|
|
||||||
if exchange_name == "bybit":
|
|
||||||
params2["position_idx"] = 0
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
exchange._get_params(
|
exchange._get_params(
|
||||||
@@ -6012,7 +6015,7 @@ def test__get_params(mocker, default_conf, exchange_name):
|
|||||||
time_in_force="IOC",
|
time_in_force="IOC",
|
||||||
leverage=3.0,
|
leverage=3.0,
|
||||||
)
|
)
|
||||||
== params2
|
== params_fut
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -422,6 +422,10 @@ EXCHANGES = {
|
|||||||
"hasQuoteVolume": True,
|
"hasQuoteVolume": True,
|
||||||
"timeframe": "1h",
|
"timeframe": "1h",
|
||||||
"candle_count": 1000,
|
"candle_count": 1000,
|
||||||
|
"futures": True,
|
||||||
|
"futures_pair": "BTC/USDT:USDT",
|
||||||
|
"leverage_tiers_public": True,
|
||||||
|
"leverage_in_spot_market": True,
|
||||||
},
|
},
|
||||||
"coinex": {
|
"coinex": {
|
||||||
"pair": "BTC/USDT",
|
"pair": "BTC/USDT",
|
||||||
|
|||||||
@@ -1132,7 +1132,9 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||||
def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path, fee) -> None:
|
def test_in_strategy_auto_hyperopt_with_parallel(
|
||||||
|
mocker, hyperopt_conf, tmp_path, fee, caplog
|
||||||
|
) -> None:
|
||||||
mocker.patch(f"{EXMS}.validate_config", MagicMock())
|
mocker.patch(f"{EXMS}.validate_config", MagicMock())
|
||||||
mocker.patch(f"{EXMS}.get_fee", fee)
|
mocker.patch(f"{EXMS}.get_fee", fee)
|
||||||
mocker.patch(f"{EXMS}.reload_markets")
|
mocker.patch(f"{EXMS}.reload_markets")
|
||||||
@@ -1175,6 +1177,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
|
|||||||
assert len(list(buy_rsi_range)) == 51
|
assert len(list(buy_rsi_range)) == 51
|
||||||
|
|
||||||
hyperopt.start()
|
hyperopt.start()
|
||||||
|
# Test logs from parallel workers are shown.
|
||||||
|
assert log_has("Test: Bot loop started", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fee) -> None:
|
def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fee) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from strategy_test_v3 import StrategyTestV3
|
from strategy_test_v3 import StrategyTestV3
|
||||||
|
|
||||||
@@ -7,6 +9,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
|
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HyperoptableStrategy(StrategyTestV3):
|
class HyperoptableStrategy(StrategyTestV3):
|
||||||
"""
|
"""
|
||||||
Default Strategy provided by freqtrade bot.
|
Default Strategy provided by freqtrade bot.
|
||||||
@@ -16,6 +21,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
for samples and inspiration.
|
for samples and inspiration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
INTERFACE_VERSION = 3
|
||||||
buy_params = {
|
buy_params = {
|
||||||
"buy_rsi": 35,
|
"buy_rsi": 35,
|
||||||
# Intentionally not specified, so "default" is tested
|
# Intentionally not specified, so "default" is tested
|
||||||
@@ -54,34 +60,13 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
|
|
||||||
def bot_loop_start(self, **kwargs):
|
def bot_loop_start(self, **kwargs):
|
||||||
self.bot_loop_started = True
|
self.bot_loop_started = True
|
||||||
|
logger.info("Test: Bot loop started")
|
||||||
|
|
||||||
def bot_start(self, **kwargs) -> None:
|
def bot_start(self, **kwargs) -> None:
|
||||||
"""
|
|
||||||
Parameters can also be defined here ...
|
|
||||||
"""
|
|
||||||
self.bot_started = True
|
self.bot_started = True
|
||||||
self.buy_rsi = IntParameter([0, 50], default=30, space="buy")
|
self.buy_rsi = IntParameter([0, 50], default=30, space="buy")
|
||||||
|
|
||||||
def informative_pairs(self):
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
|
||||||
Define additional, informative pair/interval combinations to be cached from the exchange.
|
|
||||||
These pair/interval combinations are non-tradeable, unless they are part
|
|
||||||
of the whitelist as well.
|
|
||||||
For more information, please consult the documentation
|
|
||||||
:return: List of tuples in the format (pair, interval)
|
|
||||||
Sample: return [("ETH/USDT", "5m"),
|
|
||||||
("BTC/USDT", "15m"),
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
"""
|
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
|
||||||
:param dataframe: DataFrame
|
|
||||||
:param metadata: Additional information, like the currently traded pair
|
|
||||||
:return: DataFrame with buy column
|
|
||||||
"""
|
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe["rsi"] < self.buy_rsi.value)
|
(dataframe["rsi"] < self.buy_rsi.value)
|
||||||
@@ -90,18 +75,12 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
& (dataframe["plus_di"] > self.buy_plusdi.value)
|
& (dataframe["plus_di"] > self.buy_plusdi.value)
|
||||||
)
|
)
|
||||||
| ((dataframe["adx"] > 65) & (dataframe["plus_di"] > self.buy_plusdi.value)),
|
| ((dataframe["adx"] > 65) & (dataframe["plus_di"] > self.buy_plusdi.value)),
|
||||||
"buy",
|
"enter_long",
|
||||||
] = 1
|
] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
|
||||||
:param dataframe: DataFrame
|
|
||||||
:param metadata: Additional information, like the currently traded pair
|
|
||||||
:return: DataFrame with sell column
|
|
||||||
"""
|
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
@@ -112,6 +91,6 @@ class HyperoptableStrategy(StrategyTestV3):
|
|||||||
& (dataframe["minus_di"] > 0)
|
& (dataframe["minus_di"] > 0)
|
||||||
)
|
)
|
||||||
| ((dataframe["adx"] > 70) & (dataframe["minus_di"] > self.sell_minusdi.value)),
|
| ((dataframe["adx"] > 70) & (dataframe["minus_di"] > self.sell_minusdi.value)),
|
||||||
"sell",
|
"exit_long",
|
||||||
] = 1
|
] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|||||||
Reference in New Issue
Block a user