From c3032feaf7e3e974cb9cc743f559ebba170a807c Mon Sep 17 00:00:00 2001 From: Alexander Malysh Date: Thu, 14 Nov 2024 10:25:14 +0000 Subject: [PATCH 01/92] * allow json in env variables --- freqtrade/configuration/environment_vars.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py index 37445538deb..5baa2bfc34c 100644 --- a/freqtrade/configuration/environment_vars.py +++ b/freqtrade/configuration/environment_vars.py @@ -1,3 +1,4 @@ +import json import logging import os from typing import Any @@ -20,6 +21,11 @@ def _get_var_typed(val): return True elif val.lower() in ("f", "false"): return False + # try to convert from json + try: + return json.loads(val) + except json.decoder.JSONDecodeError: + pass # keep as string return val From 92e64927b01b3c7d295ea953a140caa76d4e0bc5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 07:02:54 +0100 Subject: [PATCH 02/92] feat: allow object as dry-run balance --- build_helpers/schema.json | 13 +++++++++++-- freqtrade/configuration/config_schema.py | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index e12b0bf0d58..73c06ba46b3 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -102,8 +102,17 @@ }, "dry_run_wallet": { "description": "Initial wallet balance for dry run mode.", - "type": "number", - "default": 1000 + "type": [ + "number", + "object" + ], + "default": 1000, + "patternProperties": { + "^[a-zA-Z0-9]+$": { + "type": "number" + } + }, + "additionalProperties": false }, "cancel_open_orders_on_exit": { "description": "Cancel open orders when exiting.", diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 30f1f6f28a1..d31069e7b5d 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -85,8 +85,10 @@ }, "dry_run_wallet": { "description": "Initial wallet balance for dry run mode.", - "type": "number", + "type": ["number", "object"], "default": DRY_RUN_WALLET, + "patternProperties": {r"^[a-zA-Z0-9]+$": {"type": "number"}}, + "additionalProperties": False, }, "cancel_open_orders_on_exit": { "description": "Cancel open orders when exiting.", From 5b0be7e1a9f94a31d1336dcb30f280c712d442c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 07:03:54 +0100 Subject: [PATCH 03/92] feat: support dict like dry_run_wallet --- freqtrade/wallets.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 2b26fb8b639..f2cff879231 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -41,7 +41,14 @@ def __init__(self, config: Config, exchange: Exchange, is_backtest: bool = False self._exchange = exchange self._wallets: dict[str, Wallet] = {} self._positions: dict[str, PositionWallet] = {} - self._start_cap = config["dry_run_wallet"] + self._start_cap: dict[str, float] = {} + self._stake_currency = config["stake_currency"] + + if isinstance(_start_cap := config["dry_run_wallet"], float | int): + self._start_cap[self._stake_currency] = _start_cap + else: + self._start_cap = _start_cap + self._last_wallet_refresh: datetime | None = None self.update() @@ -112,7 +119,7 @@ def _update_dry(self) -> None: _wallets[curr] = Wallet(curr, trade.amount - pending, pending, trade.amount) - current_stake = self._start_cap + tot_profit - tot_in_trades + current_stake = self._start_cap[self._stake_currency] + tot_profit - tot_in_trades total_stake = current_stake + used_stake else: tot_in_trades = 0 @@ -129,12 +136,13 @@ def _update_dry(self) -> None: collateral=collateral, side=position.trade_direction, ) - current_stake = self._start_cap + tot_profit - tot_in_trades + current_stake = self._start_cap[self._stake_currency] + tot_profit - tot_in_trades + used_stake = tot_in_trades total_stake = current_stake + tot_in_trades - _wallets[self._config["stake_currency"]] = Wallet( - currency=self._config["stake_currency"], + _wallets[self._stake_currency] = Wallet( + currency=self._stake_currency, free=current_stake, used=used_stake, total=total_stake, From b4b6de4e0da6598ce458db9f5115a3ac143e2eb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 07:11:26 +0100 Subject: [PATCH 04/92] test: update test ... --- tests/test_wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 2c12d27b507..3b0a88bad3c 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -168,7 +168,7 @@ def test_get_trade_stake_amount_unlimited_amount( assert result == 0 freqtrade.config["dry_run_wallet"] = 200 - freqtrade.wallets._start_cap = 200 + freqtrade.wallets._start_cap["BTC"] = 200 result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 3) assert round(result, 4) == round(result2, 4) From 15c1a8ee0bf98eab4909796ddd941fa9f7edbadb Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 07:13:05 +0100 Subject: [PATCH 05/92] chore: reduce dict lookups, reuse attribute --- freqtrade/wallets.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index f2cff879231..ec79e25e338 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -252,7 +252,7 @@ def get_starting_balance(self) -> float: else: tot_profit = Trade.get_total_closed_profit() open_stakes = Trade.total_open_trades_stakes() - available_balance = self.get_free(self._config["stake_currency"]) + available_balance = self.get_free(self._stake_currency) return available_balance - tot_profit + open_stakes def get_total_stake_amount(self): @@ -272,9 +272,9 @@ def get_total_stake_amount(self): # Ensure % is used from the overall balance # Otherwise we'd risk lowering stakes with each open trade. # (tied up + current free) * ratio) - tied up - available_amount = ( - val_tied_up + self.get_free(self._config["stake_currency"]) - ) * self._config["tradable_balance_ratio"] + available_amount = (val_tied_up + self.get_free(self._stake_currency)) * self._config[ + "tradable_balance_ratio" + ] return available_amount def get_available_stake_amount(self) -> float: @@ -285,7 +285,7 @@ def get_available_stake_amount(self) -> float: ( + free amount) * tradable_balance_ratio - """ - free = self.get_free(self._config["stake_currency"]) + free = self.get_free(self._stake_currency) return min(self.get_total_stake_amount() - Trade.total_open_trades_stakes(), free) def _calculate_unlimited_stake_amount( @@ -344,8 +344,8 @@ def get_trade_stake_amount( if edge: stake_amount = edge.stake_amount( pair, - self.get_free(self._config["stake_currency"]), - self.get_total(self._config["stake_currency"]), + self.get_free(self._stake_currency), + self.get_total(self._stake_currency), val_tied_up, ) else: From 3fc259bb9b57995889feb0489f8fa00f1bc52ffa Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 07:23:57 +0100 Subject: [PATCH 06/92] feat: add non-trading balance to wallet --- freqtrade/wallets.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index ec79e25e338..39b7070ecbb 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -116,8 +116,14 @@ def _update_dry(self) -> None: for o in trade.open_orders if o.amount and o.ft_order_side == trade.exit_side ) + curr_wallet_bal = self._start_cap.get(curr, 0) - _wallets[curr] = Wallet(curr, trade.amount - pending, pending, trade.amount) + _wallets[curr] = Wallet( + curr, + curr_wallet_bal + trade.amount - pending, + pending, + trade.amount + curr_wallet_bal, + ) current_stake = self._start_cap[self._stake_currency] + tot_profit - tot_in_trades total_stake = current_stake + used_stake @@ -147,6 +153,11 @@ def _update_dry(self) -> None: used=used_stake, total=total_stake, ) + for currency in self._start_cap: + if currency not in _wallets: + bal = self._start_cap[currency] + _wallets[currency] = Wallet(currency, bal, 0, bal) + self._wallets = _wallets self._positions = _positions From 37aba6f7d5a96670a776a49ddb35089240b07e27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 20:04:09 +0100 Subject: [PATCH 07/92] feat: Allow fetch_tickers from different marketsegment --- freqtrade/exchange/binance.py | 10 ++++++++-- freqtrade/exchange/exchange.py | 23 ++++++++++++++++++----- freqtrade/exchange/kraken.py | 10 ++++++++-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 64b597fe8ad..151736e1b53 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -52,8 +52,14 @@ class Binance(Exchange): (TradingMode.FUTURES, MarginMode.ISOLATED) ] - def get_tickers(self, symbols: list[str] | None = None, *, cached: bool = False) -> Tickers: - tickers = super().get_tickers(symbols=symbols, cached=cached) + def get_tickers( + self, + symbols: list[str] | None = None, + *, + cached: bool = False, + market_type: TradingMode | None = None, + ) -> Tickers: + tickers = super().get_tickers(symbols=symbols, cached=cached, market_type=market_type) if self.trading_mode == TradingMode.FUTURES: # Binance's future result has no bid/ask values. # Therefore we must fetch that from fetch_bids_asks and combine the two results. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a3006c99bc4..e7be5394b29 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -201,7 +201,7 @@ def __init__( self._cache_lock = Lock() # Cache for 10 minutes ... - self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=2, ttl=60 * 10) + self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=4, ttl=60 * 10) # Cache values for 300 to avoid frequent polling of the exchange for prices # Caching only applies to RPC methods, so prices for open trades are still # refreshed once every iteration. @@ -1801,24 +1801,37 @@ def fetch_bids_asks(self, symbols: list[str] | None = None, *, cached: bool = Fa raise OperationalException(e) from e @retrier - def get_tickers(self, symbols: list[str] | None = None, *, cached: bool = False) -> Tickers: + def get_tickers( + self, + symbols: list[str] | None = None, + *, + cached: bool = False, + market_type: TradingMode | None = None, + ) -> Tickers: """ :param symbols: List of symbols to fetch :param cached: Allow cached result + :param market_type: Market type to fetch - either spot or futures. :return: fetch_tickers result """ tickers: Tickers if not self.exchange_has("fetchTickers"): return {} + cache_key = f"fetch_tickers_{market_type}" if market_type else "fetch_tickers" if cached: with self._cache_lock: - tickers = self._fetch_tickers_cache.get("fetch_tickers") # type: ignore + tickers = self._fetch_tickers_cache.get(cache_key) # type: ignore if tickers: return tickers try: - tickers = self._api.fetch_tickers(symbols) + # Re-map futures to swap + market_types = { + TradingMode.FUTURES: "swap", + } + params = {"type": market_types.get(market_type, market_type)} if market_type else {} + tickers = self._api.fetch_tickers(symbols, params) with self._cache_lock: - self._fetch_tickers_cache["fetch_tickers"] = tickers + self._fetch_tickers_cache[cache_key] = tickers return tickers except ccxt.NotSupported as e: raise OperationalException( diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index b2035fcef00..53fce867b7b 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -50,11 +50,17 @@ def market_is_tradable(self, market: dict[str, Any]) -> bool: return parent_check and market.get("darkpool", False) is False - def get_tickers(self, symbols: list[str] | None = None, *, cached: bool = False) -> Tickers: + def get_tickers( + self, + symbols: list[str] | None = None, + *, + cached: bool = False, + market_type: TradingMode | None = None, + ) -> Tickers: # Only fetch tickers for current stake currency # Otherwise the request for kraken becomes too large. symbols = list(self.get_markets(quote_currencies=[self._config["stake_currency"]])) - return super().get_tickers(symbols=symbols, cached=cached) + return super().get_tickers(symbols=symbols, cached=cached, market_type=market_type) @retrier def get_balances(self) -> CcxtBalances: From 2bb111605c9daed19ae61200eb3b9cfb4dd31c35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 20:06:28 +0100 Subject: [PATCH 08/92] feat: update rpc_balance to fetch spot tickers Happens if there is no market for the given pair --- freqtrade/rpc/rpc.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index afa8baea2a6..d4c05d2d23d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -7,7 +7,7 @@ from collections.abc import Generator, Sequence from datetime import date, datetime, timedelta, timezone from math import isnan -from typing import Any, cast +from typing import Any import psutil from dateutil.relativedelta import relativedelta @@ -682,12 +682,23 @@ def __balance_get_est_stake( est_bot_stake = amount else: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - rate: float | None = cast(Ticker, tickers.get(pair, {})).get("last", None) - if rate: - if pair.startswith(stake_currency) and not pair.endswith(stake_currency): - rate = 1.0 / rate - est_stake = rate * balance.total - est_bot_stake = rate * amount + ticker: Ticker | None = tickers.get(pair, None) + if not ticker: + tickers_spot: Tickers = self._freqtrade.exchange.get_tickers( + cached=True, + market_type=TradingMode.SPOT + if self._config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT + else TradingMode.FUTURES, + ) + ticker = tickers_spot.get(pair, None) + + if ticker: + rate: float | None = ticker.get("last", None) + if rate: + if pair.startswith(stake_currency) and not pair.endswith(stake_currency): + rate = 1.0 / rate + est_stake = rate * balance.total + est_bot_stake = rate * amount return est_stake, est_bot_stake From e6e193f2520a824276649d7c11f90413d1154d3f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 20:17:56 +0100 Subject: [PATCH 09/92] test: assert tickers is called a 2nd time if necessary --- tests/rpc/test_rpc.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index dd8c1bb9a87..efdc18977e0 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -530,6 +530,13 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "total": 5.0, "used": 4.0, }, + # Invalid coin not in tickers list. + # This triggers a 2nd call to get_tickers + "NotACoin": { + "free": 0.0, + "total": 2.0, + "used": 0.0, + }, "USDT": { "free": 50.0, "total": 100.0, @@ -590,8 +597,10 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): assert pytest.approx(result["total"]) == 2824.83464 assert pytest.approx(result["value"]) == 2824.83464 * 1.2 - assert tickers.call_count == 1 + assert tickers.call_count == 2 assert tickers.call_args_list[0][1]["cached"] is True + # Testing futures - so we should get spot tickers + assert tickers.call_args_list[1][1]["market_type"] == "spot" assert "USD" == result["symbol"] assert result["currencies"] == [ { @@ -622,6 +631,20 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "is_bot_managed": False, "is_position": False, }, + { + "currency": "NotACoin", + "balance": 2.0, + "bot_owned": 0, + "est_stake": 0, + "est_stake_bot": 0, + "free": 0.0, + "is_bot_managed": False, + "is_position": False, + "position": 0, + "side": "long", + "stake": "USDT", + "used": 0.0, + }, { "currency": "USDT", "free": 50.0, From 7369331e2dbdc527ac24e9791e9815d418ba8fc9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2024 20:35:40 +0100 Subject: [PATCH 10/92] tests: add test for multi-pair dry-run wallets --- tests/test_wallets.py | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 3b0a88bad3c..d5e49438a47 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -451,3 +451,63 @@ def test_check_exit_amount_futures(mocker, default_conf, fee): assert freqtrade.wallets.check_exit_amount(trade) is False assert total_mock.call_count == 0 assert update_mock.call_count == 1 + + +@pytest.mark.parametrize( + "config,wallets", + [ + ( + {"stake_currency": "USDT", "dry_run_wallet": 1000.0}, + {"USDT": {"currency": "USDT", "free": 1000.0, "used": 0.0, "total": 1000.0}}, + ), + ( + {"stake_currency": "USDT", "dry_run_wallet": {"USDT": 1000.0, "BTC": 0.1, "ETH": 2.0}}, + { + "USDT": {"currency": "USDT", "free": 1000.0, "used": 0.0, "total": 1000.0}, + "BTC": {"currency": "BTC", "free": 0.1, "used": 0.0, "total": 0.1}, + "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, + }, + ), + ], +) +def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallets): + default_conf_usdt.update(config) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + # Verify each wallet matches the expected values + for currency, expected_wallet in wallets.items(): + wallet = freqtrade.wallets._wallets[currency] + assert wallet.currency == expected_wallet["currency"] + assert wallet.free == expected_wallet["free"] + assert wallet.used == expected_wallet["used"] + assert wallet.total == expected_wallet["total"] + + # Verify no extra wallets were created + assert len(freqtrade.wallets._wallets) == len(wallets) + + # Create a trade and verify the new currency is added to the wallets + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.0) + mocker.patch(f"{EXMS}.get_rate", return_value=2.22) + mocker.patch( + f"{EXMS}.fetch_ticker", + return_value={ + "bid": 0.20, + "ask": 0.22, + "last": 0.22, + }, + ) + freqtrade.execute_entry("NEO/USDT", 100.0) + + # Update wallets and verify NEO is now included + freqtrade.wallets.update() + assert "NEO" in freqtrade.wallets._wallets + + assert freqtrade.wallets._wallets["NEO"].total == 45.04504504 # 100 USDT / 0.22 + assert freqtrade.wallets._wallets["NEO"].used == 0.0 + assert freqtrade.wallets._wallets["NEO"].free == 45.04504504 + + # Verify USDT wallet was reduced by trade amount + assert ( + pytest.approx(freqtrade.wallets._wallets["USDT"].total) == wallets["USDT"]["total"] - 100.0 + ) + assert len(freqtrade.wallets._wallets) == len(wallets) + 1 # Original wallets + NEO From 671821aeb3ec1a4fdee4c94b8b9931fb323ee976 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2024 06:48:07 +0100 Subject: [PATCH 11/92] docs: Add documentation for dry-run-wallet as dict --- docs/configuration.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9c46b7546e8..cfce381761c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -168,7 +168,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). Usually missing in configuration, and specified in the strategy. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean -| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float +| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode. [More information below](#dry-run-wallet)
*Defaults to `1000`.*
**Datatype:** Float or Dict | `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions.
*Defaults to `false`.*
**Datatype:** Boolean | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean | `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to exit a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict @@ -323,6 +323,25 @@ To limit this calculation in case of large stoploss values, the calculated minim !!! Warning Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange. Freqtrade adjusts the stake-amount to this value, unless it's > 30% more than the calculated/desired stake-amount - in which case the trade is rejected. +#### Dry-run wallet + +When running in dry-run mode, the bot will use a simulated wallet to execute trades. The starting balance of this wallet is defined by `dry_run_wallet` (defaults to 1000). +For more complex scenarios, you can also assign a dictionary to `dry_run_wallet` to define the starting balance for each currency. + +```json +"dry_run_wallet": { + "BTC": 0.01, + "ETH": 2, + "USDT": 1000 +} +``` + +Command line options (`--dry-run-wallet`) can be used to override the configuration value, but only for the float value, not for the dictionary. If you'd like to use the dictionary, please adjust the configuration file. + +!!! Note + Balances not in stake-currency will not be used for trading, but are shown as part of the wallet balance. + On Cross-margin exchanges, the wallet balance may be used to calculate the available collateral for trading. + #### Tradable balance By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade. From 09308e568d1b8c40b71979b55f4417f48d3c593a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2024 07:14:11 +0100 Subject: [PATCH 12/92] fix: increase code reliability by not relying on stake-currency to be in the dict --- freqtrade/wallets.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 39b7070ecbb..9864d060448 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -125,7 +125,9 @@ def _update_dry(self) -> None: trade.amount + curr_wallet_bal, ) - current_stake = self._start_cap[self._stake_currency] + tot_profit - tot_in_trades + current_stake = ( + self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades + ) total_stake = current_stake + used_stake else: tot_in_trades = 0 @@ -142,7 +144,9 @@ def _update_dry(self) -> None: collateral=collateral, side=position.trade_direction, ) - current_stake = self._start_cap[self._stake_currency] + tot_profit - tot_in_trades + current_stake = ( + self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades + ) used_stake = tot_in_trades total_stake = current_stake + tot_in_trades From a6199680cd68b32701023a4faa74724d0583216f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Nov 2024 19:53:37 +0100 Subject: [PATCH 13/92] fix: freqAI bug causing failures on 2nd backtest --- freqtrade/freqai/data_kitchen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 5292dc0afed..cac76ece537 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -849,7 +849,7 @@ def use_strategy_to_populate_indicators( # noqa: C901 dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) dataframe = self.remove_special_chars_from_feature_names(dataframe) - self.get_unique_classes_from_labels(dataframe) + self.get_unique_classes_from_labels(dataframe) if self.config.get("reduce_df_footprint", False): dataframe = reduce_dataframe_footprint(dataframe) From 6c9e6cc1983db4919743034b6d0c1bf119f554f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 06:00:19 +0000 Subject: [PATCH 14/92] chore(deps): bump xgboost from 2.0.3 to 2.1.3 Bumps [xgboost](https://github.com/dmlc/xgboost) from 2.0.3 to 2.1.3. - [Release notes](https://github.com/dmlc/xgboost/releases) - [Changelog](https://github.com/dmlc/xgboost/blob/master/NEWS.md) - [Commits](https://github.com/dmlc/xgboost/compare/v2.0.3...v2.1.3) --- updated-dependencies: - dependency-name: xgboost dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index ca71810043d..47c69ff6471 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -10,6 +10,6 @@ catboost==1.2.7; 'arm' not in platform_machine # Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551 matplotlib==3.9.3 lightgbm==4.5.0 -xgboost==2.0.3 +xgboost==2.1.3 tensorboard==2.18.0 datasieve==0.1.7 From c841146968528b4b56466b045e513d240ca0c407 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Dec 2024 07:12:30 +0100 Subject: [PATCH 15/92] chore: Remove non-suppored callback from XGBRFRegressor According to the XGBoost docs, this probably never worked as it was never supported - but is now raising an error for user clarity. --- .../freqai/prediction_models/XGBoostRFRegressor.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py index 12231ed13f3..140186075f1 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py +++ b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py @@ -5,7 +5,6 @@ from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.data_kitchen import FreqaiDataKitchen -from freqtrade.freqai.tensorboard import TBCallback logger = logging.getLogger(__name__) @@ -45,7 +44,12 @@ def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any: model = XGBRFRegressor(**self.model_training_parameters) - model.set_params(callbacks=[TBCallback(dk.data_path)]) + # Callbacks are not supported for XGBRFRegressor, and version 2.1.x started to throw + # the following error: + # NotImplementedError: `early_stopping_rounds` and `callbacks` are not implemented + # for random forest. + + # model.set_params(callbacks=[TBCallback(dk.data_path)]) model.fit( X=X, y=y, @@ -55,6 +59,6 @@ def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any: xgb_model=xgb_model, ) # set the callbacks to empty so that we can serialize to disk later - model.set_params(callbacks=[]) + # model.set_params(callbacks=[]) return model From 57d5a55ca05b4d595f421e0ea95589ae69683f55 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 3 Dec 2024 03:17:35 +0000 Subject: [PATCH 16/92] chore: update pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbc627c33cc..3504f3b68d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.8.0' + rev: 'v0.8.1' hooks: - id: ruff - id: ruff-format From ef0703f2092198879e7006824356c960ac953174 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 06:53:04 +0100 Subject: [PATCH 17/92] fix: Set timeframe for api calls closes #11009 --- freqtrade/rpc/api_server/api_v1.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 5722ad15790..552bee9cd16 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -357,6 +357,7 @@ def pair_history( config = deepcopy(config) config.update( { + "timeframe": timeframe, "strategy": strategy, "timerange": timerange, "freqaimodel": freqaimodel if freqaimodel else config.get("freqaimodel"), @@ -377,6 +378,7 @@ def pair_history_filtered( config = deepcopy(config) config.update( { + "timeframe": payload.timeframe, "strategy": payload.strategy, "timerange": payload.timerange, "freqaimodel": ( From c082e5f6a6f5271bf26ba3ed4bc0ec94ed8d2c0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 07:26:47 +0100 Subject: [PATCH 18/92] feat: add dry_run_wallet helper --- freqtrade/util/__init__.py | 2 ++ freqtrade/util/dry_run_wallet.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 freqtrade/util/dry_run_wallet.py diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 7a4d4d1196c..a0b618b11f7 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -11,6 +11,7 @@ format_ms_time, shorten_date, ) +from freqtrade.util.dry_run_wallet import get_dry_run_wallet from freqtrade.util.formatters import decimals_per_coin, fmt_coin, fmt_coin2, round_value from freqtrade.util.ft_precise import FtPrecise from freqtrade.util.measure_time import MeasureTime @@ -35,6 +36,7 @@ "dt_utc", "format_date", "format_ms_time", + "get_dry_run_wallet", "FtPrecise", "PeriodicCache", "shorten_date", diff --git a/freqtrade/util/dry_run_wallet.py b/freqtrade/util/dry_run_wallet.py new file mode 100644 index 00000000000..f994f0d4cb1 --- /dev/null +++ b/freqtrade/util/dry_run_wallet.py @@ -0,0 +1,12 @@ +from pytest import Config + + +def get_dry_run_wallet(config: Config) -> int | float: + """ + Return dry-run wallet balance in stake currency from configuration. + This setup also supports dictionary mode for dry-run-wallet. + """ + if isinstance(_start_cap := config["dry_run_wallet"], float | int): + return _start_cap + else: + return _start_cap.get("stake_currency") From 7a8971b9b6051a20978355048f1929a03b0e46d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 07:27:45 +0100 Subject: [PATCH 19/92] feat: use get_dry_run_wallet helper --- freqtrade/commands/optimize_commands.py | 3 ++- freqtrade/optimize/analysis/lookahead_helpers.py | 4 ++-- freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py | 3 ++- .../hyperopt_loss/hyperopt_loss_max_drawdown_relative.py | 3 ++- .../optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 3 ++- .../optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py | 3 ++- freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py | 3 ++- freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py | 3 ++- freqtrade/optimize/optimize_reports/optimize_reports.py | 4 ++-- freqtrade/plot/plotting.py | 3 ++- 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index ea75f3bd173..788bca48961 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -4,6 +4,7 @@ from freqtrade import constants from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException +from freqtrade.util import get_dry_run_wallet logger = logging.getLogger(__name__) @@ -26,7 +27,7 @@ def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[ RunMode.HYPEROPT: "hyperoptimization", } if method in no_unlimited_runmodes.keys(): - wallet_size = config["dry_run_wallet"] * config["tradable_balance_ratio"] + wallet_size = get_dry_run_wallet(config) * config["tradable_balance_ratio"] # tradable_balance_ratio if ( config["stake_amount"] != constants.UNLIMITED_STAKE_AMOUNT diff --git a/freqtrade/optimize/analysis/lookahead_helpers.py b/freqtrade/optimize/analysis/lookahead_helpers.py index d664f9635c3..631a9549f8b 100644 --- a/freqtrade/optimize/analysis/lookahead_helpers.py +++ b/freqtrade/optimize/analysis/lookahead_helpers.py @@ -10,7 +10,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis from freqtrade.resolvers import StrategyResolver -from freqtrade.util import print_rich_table +from freqtrade.util import get_dry_run_wallet, print_rich_table logger = logging.getLogger(__name__) @@ -163,7 +163,7 @@ def calculate_config_overrides(config: Config): config["max_open_trades"] = len(config["pairs"]) min_dry_run_wallet = 1000000000 - if config["dry_run_wallet"] < min_dry_run_wallet: + if get_dry_run_wallet(config) < min_dry_run_wallet: logger.info( "Dry run wallet was not set to 1 billion, pushing it up there " "just to avoid false positives" diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py index f22d59e50b5..6072629c4e5 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py @@ -12,6 +12,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_calmar from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util import get_dry_run_wallet class CalmarHyperOptLoss(IHyperOptLoss): @@ -36,7 +37,7 @@ def hyperopt_loss_function( Uses Calmar Ratio calculation. """ - starting_balance = config["dry_run_wallet"] + starting_balance = get_dry_run_wallet(config) calmar_ratio = calculate_calmar(results, min_date, max_date, starting_balance) # print(expected_returns_mean, max_drawdown, calmar_ratio) return -calmar_ratio diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py index ee7088d755e..4bbbcf7d3ff 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py @@ -10,6 +10,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_underwater from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util import get_dry_run_wallet class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): @@ -31,7 +32,7 @@ def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) total_profit = results["profit_abs"].sum() try: drawdown_df = calculate_underwater( - results, value_col="profit_abs", starting_balance=config["dry_run_wallet"] + results, value_col="profit_abs", starting_balance=get_dry_run_wallet(config) ) max_drawdown = abs(min(drawdown_df["drawdown"])) relative_drawdown = max(drawdown_df["drawdown_relative"]) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index de8d117d6ae..dd5fa4a17d1 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -36,6 +36,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util import get_dry_run_wallet # smaller numbers penalize drawdowns more severely @@ -83,7 +84,7 @@ def hyperopt_loss_function( # Calculate drawdown try: drawdown = calculate_max_drawdown( - results, starting_balance=config["dry_run_wallet"], value_col="profit_abs" + results, starting_balance=get_dry_run_wallet(config), value_col="profit_abs" ) relative_account_drawdown = drawdown.relative_account_drawdown except ValueError: diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py index 61e2a6d3266..5230408a962 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py @@ -13,6 +13,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util import get_dry_run_wallet # smaller numbers penalize drawdowns more severely @@ -26,7 +27,7 @@ def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) try: drawdown = calculate_max_drawdown( - results, starting_balance=config["dry_run_wallet"], value_col="profit_abs" + results, starting_balance=get_dry_run_wallet(config), value_col="profit_abs" ) relative_account_drawdown = drawdown.relative_account_drawdown except ValueError: diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py index 2c7042a8a71..4806ddb7f46 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py @@ -12,6 +12,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_sharpe from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util.dry_run_wallet import get_dry_run_wallet class SharpeHyperOptLoss(IHyperOptLoss): @@ -36,7 +37,7 @@ def hyperopt_loss_function( Uses Sharpe Ratio calculation. """ - starting_balance = config["dry_run_wallet"] + starting_balance = get_dry_run_wallet(config) sharp_ratio = calculate_sharpe(results, min_date, max_date, starting_balance) # print(expected_returns_mean, up_stdev, sharp_ratio) return -sharp_ratio diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py index 32ff0c73f69..c01be889d49 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py @@ -12,6 +12,7 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_sortino from freqtrade.optimize.hyperopt import IHyperOptLoss +from freqtrade.util import get_dry_run_wallet class SortinoHyperOptLoss(IHyperOptLoss): @@ -36,7 +37,7 @@ def hyperopt_loss_function( Uses Sortino Ratio calculation. """ - starting_balance = config["dry_run_wallet"] + starting_balance = get_dry_run_wallet(config) sortino_ratio = calculate_sortino(results, min_date, max_date, starting_balance) # print(expected_returns_mean, down_stdev, sortino_ratio) return -sortino_ratio diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 8f1f0140ac8..532abed3925 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -18,7 +18,7 @@ calculate_sortino, ) from freqtrade.ft_types import BacktestResultType -from freqtrade.util import decimals_per_coin, fmt_coin +from freqtrade.util import decimals_per_coin, fmt_coin, get_dry_run_wallet logger = logging.getLogger(__name__) @@ -373,7 +373,7 @@ def generate_strategy_stats( return {} config = content["config"] max_open_trades = min(config["max_open_trades"], len(pairlist)) - start_balance = config["dry_run_wallet"] + start_balance = get_dry_run_wallet(config) stake_currency = config["stake_currency"] pair_results = generate_pair_metrics( diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index cf86f070ddb..8ec7c7bae6f 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -28,6 +28,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.strategy import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper +from freqtrade.util import get_dry_run_wallet logger = logging.getLogger(__name__) @@ -706,7 +707,7 @@ def plot_profit(config: Config) -> None: trades, config["timeframe"], config.get("stake_currency", ""), - config.get("available_capital", config["dry_run_wallet"]), + config.get("available_capital", get_dry_run_wallet(config)), ) store_plot_file( fig, From b0b73bf166d7545f361524ca3b714f582c50de79 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 19:15:57 +0100 Subject: [PATCH 20/92] feat: add starting_balance as argument to hyperopt_loss_function --- freqtrade/optimize/hyperopt/hyperopt_optimizer.py | 2 ++ freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py | 3 +-- .../optimize/hyperopt_loss/hyperopt_loss_interface.py | 1 + .../hyperopt_loss/hyperopt_loss_max_drawdown_relative.py | 7 ++++--- .../optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 4 ++-- .../hyperopt_loss/hyperopt_loss_profit_drawdown.py | 7 ++++--- freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py | 6 +----- freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py | 6 +----- tests/optimize/test_hyperoptloss.py | 1 + 9 files changed, 17 insertions(+), 20 deletions(-) diff --git a/freqtrade/optimize/hyperopt/hyperopt_optimizer.py b/freqtrade/optimize/hyperopt/hyperopt_optimizer.py index bd234aa5701..c8d18d2248b 100644 --- a/freqtrade/optimize/hyperopt/hyperopt_optimizer.py +++ b/freqtrade/optimize/hyperopt/hyperopt_optimizer.py @@ -28,6 +28,7 @@ from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer, HyperoptTools from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver +from freqtrade.util.dry_run_wallet import get_dry_run_wallet # Suppress scikit-learn FutureWarnings from skopt @@ -363,6 +364,7 @@ def _get_results_dict( config=self.config, processed=processed, backtest_stats=strat_stats, + starting_balance=get_dry_run_wallet(self.config), ) return { "loss": loss, diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py index 6072629c4e5..2a04d40707d 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py @@ -12,7 +12,6 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_calmar from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util import get_dry_run_wallet class CalmarHyperOptLoss(IHyperOptLoss): @@ -29,6 +28,7 @@ def hyperopt_loss_function( min_date: datetime, max_date: datetime, config: Config, + starting_balance: float, *args, **kwargs, ) -> float: @@ -37,7 +37,6 @@ def hyperopt_loss_function( Uses Calmar Ratio calculation. """ - starting_balance = get_dry_run_wallet(config) calmar_ratio = calculate_calmar(results, min_date, max_date, starting_balance) # print(expected_returns_mean, max_drawdown, calmar_ratio) return -calmar_ratio diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_interface.py index a48fee7313d..ddc96d6a0ea 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_interface.py @@ -31,6 +31,7 @@ def hyperopt_loss_function( config: Config, processed: dict[str, DataFrame], backtest_stats: dict[str, Any], + starting_balance: float, **kwargs, ) -> float: """ diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py index 4bbbcf7d3ff..a753263a3ab 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py @@ -10,7 +10,6 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_underwater from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util import get_dry_run_wallet class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): @@ -22,7 +21,9 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): """ @staticmethod - def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) -> float: + def hyperopt_loss_function( + results: DataFrame, config: Config, starting_balance: float, *args, **kwargs + ) -> float: """ Objective function. @@ -32,7 +33,7 @@ def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) total_profit = results["profit_abs"].sum() try: drawdown_df = calculate_underwater( - results, value_col="profit_abs", starting_balance=get_dry_run_wallet(config) + results, value_col="profit_abs", starting_balance=starting_balance ) max_drawdown = abs(min(drawdown_df["drawdown"])) relative_drawdown = max(drawdown_df["drawdown_relative"]) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index dd5fa4a17d1..7a10d279b8b 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -36,7 +36,6 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util import get_dry_run_wallet # smaller numbers penalize drawdowns more severely @@ -59,6 +58,7 @@ def hyperopt_loss_function( results: DataFrame, trade_count: int, config: Config, + starting_balance: float, **kwargs, ) -> float: total_profit = results["profit_abs"].sum() @@ -84,7 +84,7 @@ def hyperopt_loss_function( # Calculate drawdown try: drawdown = calculate_max_drawdown( - results, starting_balance=get_dry_run_wallet(config), value_col="profit_abs" + results, starting_balance=starting_balance, value_col="profit_abs" ) relative_account_drawdown = drawdown.relative_account_drawdown except ValueError: diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py index 5230408a962..2ab581520a8 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py @@ -13,7 +13,6 @@ from freqtrade.constants import Config from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util import get_dry_run_wallet # smaller numbers penalize drawdowns more severely @@ -22,12 +21,14 @@ class ProfitDrawDownHyperOptLoss(IHyperOptLoss): @staticmethod - def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) -> float: + def hyperopt_loss_function( + results: DataFrame, config: Config, starting_balance: float, *args, **kwargs + ) -> float: total_profit = results["profit_abs"].sum() try: drawdown = calculate_max_drawdown( - results, starting_balance=get_dry_run_wallet(config), value_col="profit_abs" + results, starting_balance=starting_balance, value_col="profit_abs" ) relative_account_drawdown = drawdown.relative_account_drawdown except ValueError: diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py index 4806ddb7f46..20e4ee2b6c6 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py @@ -9,10 +9,8 @@ from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_sharpe from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util.dry_run_wallet import get_dry_run_wallet class SharpeHyperOptLoss(IHyperOptLoss): @@ -25,10 +23,9 @@ class SharpeHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( results: DataFrame, - trade_count: int, min_date: datetime, max_date: datetime, - config: Config, + starting_balance: float, *args, **kwargs, ) -> float: @@ -37,7 +34,6 @@ def hyperopt_loss_function( Uses Sharpe Ratio calculation. """ - starting_balance = get_dry_run_wallet(config) sharp_ratio = calculate_sharpe(results, min_date, max_date, starting_balance) # print(expected_returns_mean, up_stdev, sharp_ratio) return -sharp_ratio diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py index c01be889d49..935d038e554 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py @@ -9,10 +9,8 @@ from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_sortino from freqtrade.optimize.hyperopt import IHyperOptLoss -from freqtrade.util import get_dry_run_wallet class SortinoHyperOptLoss(IHyperOptLoss): @@ -25,10 +23,9 @@ class SortinoHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( results: DataFrame, - trade_count: int, min_date: datetime, max_date: datetime, - config: Config, + starting_balance: float, *args, **kwargs, ) -> float: @@ -37,7 +34,6 @@ def hyperopt_loss_function( Uses Sortino Ratio calculation. """ - starting_balance = get_dry_run_wallet(config) sortino_ratio = calculate_sortino(results, min_date, max_date, starting_balance) # print(expected_returns_mean, down_stdev, sortino_ratio) return -sortino_ratio diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index 53de37a0e2a..9634bd1174a 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -116,6 +116,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct config=default_conf, processed=None, backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=default_conf["dry_run_wallet"], ) over = hl.hyperopt_loss_function( results_over, From 18305a5bf6b163b6d7609ee4ff953f9a3b2dfcce Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 19:16:48 +0100 Subject: [PATCH 21/92] docs: update hyperopt docs to include new argument --- docs/advanced-hyperopt.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index 480b20daf74..b12d8f5b662 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -39,6 +39,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss): config: Config, processed: dict[str, DataFrame], backtest_stats: dict[str, Any], + starting_balance: float, **kwargs, ) -> float: """ @@ -70,6 +71,7 @@ Currently, the arguments are: * `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space). * `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting. * `backtest_stats`: Backtesting statistics using the same format as the backtesting file "strategy" substructure. Available fields can be seen in `generate_strategy_stats()` in `optimize_reports.py`. +* `starting_balance`: Starting balance used for backtesting. This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you. From ad8f62128778d2ddf6191485755f5c5356754c8f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 19:17:01 +0100 Subject: [PATCH 22/92] tests: Improve hyperopt loss tests --- tests/optimize/test_hyperoptloss.py | 84 +++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index 9634bd1174a..8f1b1c7867e 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -39,13 +39,34 @@ def test_loss_calculation_prefer_correct_trade_count(hyperopt_conf, hyperopt_res hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) correct = hl.hyperopt_loss_function( - hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=hyperopt_results, + trade_count=600, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) over = hl.hyperopt_loss_function( - hyperopt_results, 600 + 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=hyperopt_results, + trade_count=600 + 100, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) under = hl.hyperopt_loss_function( - hyperopt_results, 600 - 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=hyperopt_results, + trade_count=600 - 100, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) assert over > correct assert under > correct @@ -58,9 +79,25 @@ def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results) hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) longer = hl.hyperopt_loss_function( - hyperopt_results, 100, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=hyperopt_results, + trade_count=100, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], + ) + shorter = hl.hyperopt_loss_function( + results=resultsb, + trade_count=100, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": resultsb["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) - shorter = hl.hyperopt_loss_function(resultsb, 100, datetime(2019, 1, 1), datetime(2019, 5, 1)) assert shorter < longer @@ -73,11 +110,34 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"}) hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf) correct = hl.hyperopt_loss_function( - hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=hyperopt_results, + trade_count=600, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], + ) + over = hl.hyperopt_loss_function( + results=results_over, + trade_count=600, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": results_over["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) - over = hl.hyperopt_loss_function(results_over, 600, datetime(2019, 1, 1), datetime(2019, 5, 1)) under = hl.hyperopt_loss_function( - results_under, 600, datetime(2019, 1, 1), datetime(2019, 5, 1) + results=results_under, + trade_count=600, + min_date=datetime(2019, 1, 1), + max_date=datetime(2019, 5, 1), + config=hyperopt_conf, + processed=None, + backtest_stats={"profit_total": results_under["profit_abs"].sum()}, + starting_balance=hyperopt_conf["dry_run_wallet"], ) assert over < correct assert under > correct @@ -109,7 +169,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct default_conf.update({"hyperopt_loss": lossfunction}) hl = HyperOptLossResolver.load_hyperoptloss(default_conf) correct = hl.hyperopt_loss_function( - hyperopt_results, + results=hyperopt_results, trade_count=len(hyperopt_results), min_date=datetime(2019, 1, 1), max_date=datetime(2019, 5, 1), @@ -119,22 +179,24 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct starting_balance=default_conf["dry_run_wallet"], ) over = hl.hyperopt_loss_function( - results_over, + results=results_over, trade_count=len(results_over), min_date=datetime(2019, 1, 1), max_date=datetime(2019, 5, 1), config=default_conf, processed=None, backtest_stats={"profit_total": results_over["profit_abs"].sum()}, + starting_balance=default_conf["dry_run_wallet"], ) under = hl.hyperopt_loss_function( - results_under, + results=results_under, trade_count=len(results_under), min_date=datetime(2019, 1, 1), max_date=datetime(2019, 5, 1), config=default_conf, processed=None, backtest_stats={"profit_total": results_under["profit_abs"].sum()}, + starting_balance=default_conf["dry_run_wallet"], ) assert over < correct assert under > correct From fe834f00a2b0aeb02e1bfc895279d258ce9bb41c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 19:37:14 +0100 Subject: [PATCH 23/92] fix: import causing long startup time --- freqtrade/commands/optimize_commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index 788bca48961..9695a313b9c 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -4,7 +4,6 @@ from freqtrade import constants from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException -from freqtrade.util import get_dry_run_wallet logger = logging.getLogger(__name__) @@ -18,7 +17,7 @@ def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[ :return: Configuration """ from freqtrade.configuration import setup_utils_configuration - from freqtrade.util import fmt_coin + from freqtrade.util import fmt_coin, get_dry_run_wallet config = setup_utils_configuration(args, method) From ebae0a7248ca37d3af8519bce45d1c5b3b84a2af Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 19:44:26 +0100 Subject: [PATCH 24/92] chore: improve typing of new functionality --- freqtrade/optimize/optimize_reports/optimize_reports.py | 6 +++--- freqtrade/util/dry_run_wallet.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 532abed3925..23119077bfb 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -69,7 +69,7 @@ def generate_rejected_signals( def _generate_result_line( - result: DataFrame, starting_balance: int, first_column: str | list[str] + result: DataFrame, starting_balance: float, first_column: str | list[str] ) -> dict: """ Generate one result dict, with "first_column" as key. @@ -111,7 +111,7 @@ def _generate_result_line( def generate_pair_metrics( pairlist: list[str], stake_currency: str, - starting_balance: int, + starting_balance: float, results: DataFrame, skip_nan: bool = False, ) -> list[dict]: @@ -144,7 +144,7 @@ def generate_pair_metrics( def generate_tag_metrics( tag_type: Literal["enter_tag", "exit_reason"] | list[Literal["enter_tag", "exit_reason"]], - starting_balance: int, + starting_balance: float, results: DataFrame, skip_nan: bool = False, ) -> list[dict]: diff --git a/freqtrade/util/dry_run_wallet.py b/freqtrade/util/dry_run_wallet.py index f994f0d4cb1..c904db6b397 100644 --- a/freqtrade/util/dry_run_wallet.py +++ b/freqtrade/util/dry_run_wallet.py @@ -1,4 +1,4 @@ -from pytest import Config +from freqtrade.constants import Config def get_dry_run_wallet(config: Config) -> int | float: From d6c21e2af65bc29105bbb277874dfc22fab24a87 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Dec 2024 20:13:25 +0100 Subject: [PATCH 25/92] chore: orderflow: better typing --- freqtrade/data/converter/orderflow.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index e64caa88b99..ff43d1c228e 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -102,6 +102,7 @@ def populate_dataframe_with_trades( stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object) trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) + candle_start: datetime for candle_start, trades_grouped_df in trades_grouped_by_candle_start: is_between = candle_start == dataframe["date"] if is_between.any(): @@ -181,9 +182,9 @@ def populate_dataframe_with_trades( dataframe.loc[indices, "total_trades"] = len(trades_grouped_df) # Cache the result - cached_grouped_trades[(typing.cast(datetime, candle_start), candle_next)] = ( - dataframe.loc[is_between].copy() - ) + cached_grouped_trades[(candle_start, candle_next)] = dataframe.loc[ + is_between + ].copy() # Maintain cache size if ( From 7dfee16e258dcacf90c426af0a89db4eaa211aa3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Dec 2024 07:09:09 +0100 Subject: [PATCH 26/92] chore: improved logic ordering in orderflow method --- freqtrade/data/converter/orderflow.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index ff43d1c228e..03775f97bfb 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -115,13 +115,6 @@ def populate_dataframe_with_trades( f"might be unfinished, because no finished trades at {candle_next}" ) - indices = dataframe.index[is_between].tolist() - # Add trades to each candle - trades_series.loc[indices] = [ - trades_grouped_df.drop(columns=["candle_start", "candle_end"]).to_dict( - orient="records" - ) - ] # Use caching mechanism if (candle_start, candle_next) in cached_grouped_trades: cache_entry = cached_grouped_trades[ @@ -136,6 +129,13 @@ def populate_dataframe_with_trades( ) continue + indices = dataframe.index[is_between].tolist() + # Add trades to each candle + trades_series.loc[indices] = [ + trades_grouped_df.drop(columns=["candle_start", "candle_end"]).to_dict( + orient="records" + ) + ] # Calculate orderflow for each candle orderflow = trades_to_volumeprofile_with_total_delta_bid_ask( trades_grouped_df, scale=config_orderflow["scale"] From e04f630f41e1e6308b430c830180a964571bed1c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Dec 2024 07:10:53 +0100 Subject: [PATCH 27/92] chore: further improve typing in orderflow --- freqtrade/data/converter/orderflow.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 03775f97bfb..0bfd7a05fe1 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -4,7 +4,6 @@ import logging import time -import typing from collections import OrderedDict from datetime import datetime @@ -108,7 +107,7 @@ def populate_dataframe_with_trades( if is_between.any(): from freqtrade.exchange import timeframe_to_next_date - candle_next = timeframe_to_next_date(timeframe, typing.cast(datetime, candle_start)) + candle_next = timeframe_to_next_date(timeframe, candle_start) if candle_next not in trades_grouped_by_candle_start.groups: logger.warning( f"candle at {candle_start} with {len(trades_grouped_df)} trades " @@ -117,9 +116,7 @@ def populate_dataframe_with_trades( # Use caching mechanism if (candle_start, candle_next) in cached_grouped_trades: - cache_entry = cached_grouped_trades[ - (typing.cast(datetime, candle_start), candle_next) - ] + cache_entry = cached_grouped_trades[(candle_start, candle_next)] # dataframe.loc[is_between] = cache_entry # doesn't take, so we need workaround: # Create a dictionary of the column values to be assigned update_dict = {c: cache_entry[c].iat[0] for c in cache_entry.columns} From 298ce335b922c41f758da3bd99f0dbf1f62ff44a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Dec 2024 19:56:26 +0100 Subject: [PATCH 28/92] chore: eliminate duplicate trades data grouping --- freqtrade/data/converter/orderflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 0bfd7a05fe1..52256e7b49a 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -91,8 +91,6 @@ def populate_dataframe_with_trades( trades = trades.loc[trades["candle_start"] >= start_date] trades.reset_index(inplace=True, drop=True) - # group trades by candle start - trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) # Create Series to hold complex data trades_series = pd.Series(index=dataframe.index, dtype=object) orderflow_series = pd.Series(index=dataframe.index, dtype=object) @@ -100,7 +98,9 @@ def populate_dataframe_with_trades( stacked_imbalances_bid_series = pd.Series(index=dataframe.index, dtype=object) stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object) + # group trades by candle start trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) + candle_start: datetime for candle_start, trades_grouped_df in trades_grouped_by_candle_start: is_between = candle_start == dataframe["date"] From 1d0702e14364dcec0c091b1f34396844e1d3cbc2 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:16:51 +0000 Subject: [PATCH 29/92] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 861 ++++++++++++++---- 1 file changed, 679 insertions(+), 182 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 4bcdab66d2a..69bfb8016c6 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -3668,6 +3668,161 @@ } } ], + "AERO/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "AERO/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "AEVO/USDT:USDT": [ { "tier": 1.0, @@ -13777,14 +13932,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -13793,15 +13948,15 @@ "symbol": "CHR/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.015", "cum": "25.0" } }, @@ -13809,68 +13964,119 @@ "tier": 3.0, "symbol": "CHR/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "CHR/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" + } + }, + { + "tier": 5.0, + "symbol": "CHR/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "2825.0" } }, { - "tier": 4.0, + "tier": 6.0, "symbol": "CHR/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "27825.0" } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "CHR/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "1250000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "52825.0" + } + }, + { + "tier": 8.0, + "symbol": "CHR/USDT:USDT", + "currency": "USDT", + "minNotional": 1250000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "5", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "23150.0" + "notionalCap": "2500000", + "notionalFloor": "1250000", + "maintMarginRatio": "0.25", + "cum": "209075.0" } }, { - "tier": 6.0, + "tier": 9.0, "symbol": "CHR/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2500000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "398150.0" + "cum": "834075.0" } } ], @@ -26760,14 +26966,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -26776,15 +26982,15 @@ "symbol": "JOE/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "15", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.015", "cum": "25.0" } }, @@ -26792,72 +26998,106 @@ "tier": 3.0, "symbol": "JOE/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "JOE/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" + } + }, + { + "tier": 5.0, + "symbol": "JOE/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "2825.0" } }, { - "tier": 4.0, + "tier": 6.0, "symbol": "JOE/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "27825.0" } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "JOE/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "1250000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "cum": "52825.0" } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "JOE/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 1250000.0, "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalFloor": "1250000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "209075.0" } }, { - "tier": 7.0, + "tier": 9.0, "symbol": "JOE/USDT:USDT", "currency": "USDT", "minNotional": 3000000.0, @@ -26865,12 +27105,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", "notionalCap": "5000000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "959075.0" } } ], @@ -27184,6 +27424,161 @@ } } ], + "KAIA/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "KAIA/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "KAS/USDT:USDT": [ { "tier": 1.0, @@ -29347,10 +29742,10 @@ "minNotional": 0.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.03, - "maxLeverage": 20.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "0", "maintMarginRatio": "0.03", @@ -29364,10 +29759,10 @@ "minNotional": 25000.0, "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "8", "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -29413,13 +29808,13 @@ "symbol": "LOOM/USDT:USDT", "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "3000000", + "notionalCap": "1500000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", "cum": "148000.0" @@ -29429,17 +29824,17 @@ "tier": 6.0, "symbol": "LOOM/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 3500000.0, + "minNotional": 1500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3500000", - "notionalFloor": "3000000", + "notionalCap": "2000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "898000.0" + "cum": "523000.0" } } ], @@ -30244,14 +30639,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -30260,84 +30655,135 @@ "symbol": "LUNA2/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "symbol": "LUNA2/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "symbol": "LUNA2/USDT:USDT", "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" + } + }, + { + "tier": 5.0, + "symbol": "LUNA2/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" + } + }, + { + "tier": 6.0, + "symbol": "LUNA2/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "5675.0" + "cum": "27825.0" } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "LUNA2/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "1250000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "52825.0" + } + }, + { + "tier": 8.0, + "symbol": "LUNA2/USDT:USDT", + "currency": "USDT", + "minNotional": 1250000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "5", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11925.0" + "notionalCap": "2500000", + "notionalFloor": "1250000", + "maintMarginRatio": "0.25", + "cum": "209075.0" } }, { - "tier": 6.0, + "tier": 9.0, "symbol": "LUNA2/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2500000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "386925.0" + "cum": "834075.0" } } ], @@ -35954,10 +36400,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "75", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -35971,10 +36417,10 @@ "minNotional": 5000.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxLeverage": 16.0, "info": { "bracket": "2", - "initialLeverage": "50", + "initialLeverage": "16", "notionalCap": "10000", "notionalFloor": "5000", "maintMarginRatio": "0.015", @@ -35988,10 +36434,10 @@ "minNotional": 10000.0, "maxNotional": 30000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxLeverage": 14.0, "info": { "bracket": "3", - "initialLeverage": "25", + "initialLeverage": "14", "notionalCap": "30000", "notionalFloor": "10000", "maintMarginRatio": "0.02", @@ -36005,10 +36451,10 @@ "minNotional": 30000.0, "maxNotional": 60000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 12.0, "info": { "bracket": "4", - "initialLeverage": "20", + "initialLeverage": "12", "notionalCap": "60000", "notionalFloor": "30000", "maintMarginRatio": "0.025", @@ -36088,13 +36534,13 @@ "symbol": "ORBS/USDT:USDT", "currency": "USDT", "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "2000000", "notionalFloor": "1500000", "maintMarginRatio": "0.5", "cum": "500475.0" @@ -38419,10 +38865,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -38434,84 +38880,135 @@ "symbol": "QNT/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "symbol": "QNT/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "symbol": "QNT/USDT:USDT", "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" + } + }, + { + "tier": 5.0, + "symbol": "QNT/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" + } + }, + { + "tier": 6.0, + "symbol": "QNT/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "27825.0" } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "QNT/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "1250000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "52825.0" + } + }, + { + "tier": 8.0, + "symbol": "QNT/USDT:USDT", + "currency": "USDT", + "minNotional": 1250000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "5", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "notionalCap": "2500000", + "notionalFloor": "1250000", + "maintMarginRatio": "0.25", + "cum": "209075.0" } }, { - "tier": 6.0, + "tier": 9.0, "symbol": "QNT/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2500000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "834075.0" } } ], @@ -51060,10 +51557,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -51077,10 +51574,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -51094,10 +51591,10 @@ "minNotional": 25000.0, "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -51143,13 +51640,13 @@ "symbol": "XEM/USDT:USDT", "currency": "USDT", "minNotional": 2000000.0, - "maxNotional": 6000000.0, + "maxNotional": 2100000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "6000000", + "notionalCap": "2100000", "notionalFloor": "2000000", "maintMarginRatio": "0.25", "cum": "295650.0" @@ -51159,17 +51656,17 @@ "tier": 7.0, "symbol": "XEM/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 2100000.0, + "maxNotional": 2200000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "2200000", + "notionalFloor": "2100000", "maintMarginRatio": "0.5", - "cum": "1795650.0" + "cum": "820650.0" } } ], From 6be5947f69ed1b150836c8512537089e099b8303 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 06:37:19 +0100 Subject: [PATCH 30/92] chore: Move local Import out of the loop --- freqtrade/data/converter/orderflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 52256e7b49a..364f979875c 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -71,6 +71,8 @@ def populate_dataframe_with_trades( :param trades: Trades to populate with :return: Dataframe with trades populated """ + from freqtrade.exchange import timeframe_to_next_date + timeframe = config["timeframe"] config_orderflow = config["orderflow"] @@ -105,8 +107,6 @@ def populate_dataframe_with_trades( for candle_start, trades_grouped_df in trades_grouped_by_candle_start: is_between = candle_start == dataframe["date"] if is_between.any(): - from freqtrade.exchange import timeframe_to_next_date - candle_next = timeframe_to_next_date(timeframe, candle_start) if candle_next not in trades_grouped_by_candle_start.groups: logger.warning( From 0bf0e1808ca11776f8ddf856f1b81b86bf30b8ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 06:58:25 +0100 Subject: [PATCH 31/92] chore: add todo for future cleanup --- tests/data/test_converter_orderflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index 9a337da9193..88e21d7ab10 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -37,6 +37,7 @@ def populate_dataframe_with_trades_trades(testdatadir): @pytest.fixture def candles(testdatadir): + # TODO: this fixture isn't really necessary and could be removed return pd.read_json(testdatadir / "orderflow/candles.json").copy() From ff371c43e00e9b11a8174e1a15f97854715b43d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 07:10:20 +0100 Subject: [PATCH 32/92] tests: add test to ensure caching works part of #11008 --- tests/data/test_converter_orderflow.py | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index 88e21d7ab10..4b2dd17d557 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -8,6 +8,8 @@ from freqtrade.data.converter import populate_dataframe_with_trades from freqtrade.data.converter.orderflow import trades_to_volumeprofile_with_total_delta_bid_ask from freqtrade.data.converter.trade_converter import trades_list_to_df +from freqtrade.data.dataprovider import DataProvider +from tests.strategy.strats.strategy_test_v3 import StrategyTestV3 BIN_SIZE_SCALE = 0.5 @@ -483,3 +485,70 @@ def test_public_trades_testdata_sanity( "cost", "date", ] + + +def test_analyze_with_orderflow( + default_conf_usdt, + mocker, + populate_dataframe_with_trades_dataframe, + populate_dataframe_with_trades_trades, +): + ohlcv_history = populate_dataframe_with_trades_dataframe + # call without orderflow + strategy = StrategyTestV3(config=default_conf_usdt) + strategy.dp = DataProvider(default_conf_usdt, None, None) + + mocker.patch.object(strategy.dp, "trades", return_value=populate_dataframe_with_trades_trades) + + df = strategy.advise_indicators(ohlcv_history, {"pair:": "ETH/BTC"}) + assert len(df) == len(ohlcv_history) + assert "open" in df.columns + + expected_cols = [ + "trades", + "orderflow", + "imbalances", + "stacked_imbalances_bid", + "stacked_imbalances_ask", + "max_delta", + "min_delta", + "bid", + "ask", + "delta", + "total_trades", + ] + # Not expected to run - shouldn't have added orderflow columns + for col in expected_cols: + assert col not in df.columns, f"Column {col} found in df.columns" + + default_conf_usdt["exchange"]["use_public_trades"] = True + default_conf_usdt["orderflow"] = { + "cache_size": 5, + "max_candles": 5, + "scale": 0.005, + "imbalance_volume": 0, + "imbalance_ratio": 3, + "stacked_imbalance_range": 3, + } + + strategy.config = default_conf_usdt + df1 = strategy.advise_indicators(ohlcv_history, {"pair": "ETH/BTC"}) + assert len(df1) == len(ohlcv_history) + assert "open" in df1.columns + for col in expected_cols: + assert col in df1.columns, f"Column {col} not found in df.columns" + + if col not in ("stacked_imbalances_bid", "stacked_imbalances_ask"): + assert df1[col].count() == 5, f"Column {col} has {df1[col].count()} non-NaN values" + + # Ensure caching works - call the same logic again. + df2 = strategy.advise_indicators(ohlcv_history, {"pair": "ETH/BTC"}) + assert len(df2) == len(ohlcv_history) + assert "open" in df2.columns + for col in expected_cols: + assert col in df2.columns, f"Round2: Column {col} not found in df.columns" + + if col not in ("stacked_imbalances_bid", "stacked_imbalances_ask"): + assert ( + df2[col].count() == 5 + ), f"Round2: Column {col} has {df2[col].count()} non-NaN values" From 82d517fcbb1931e3986564d00daa786b3bf50073 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 07:12:35 +0100 Subject: [PATCH 33/92] test: improve orderflow test --- tests/data/test_converter_orderflow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index 4b2dd17d557..42398ead996 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -503,6 +503,7 @@ def test_analyze_with_orderflow( df = strategy.advise_indicators(ohlcv_history, {"pair:": "ETH/BTC"}) assert len(df) == len(ohlcv_history) assert "open" in df.columns + pair = "ETH/BTC" expected_cols = [ "trades", @@ -532,7 +533,8 @@ def test_analyze_with_orderflow( } strategy.config = default_conf_usdt - df1 = strategy.advise_indicators(ohlcv_history, {"pair": "ETH/BTC"}) + # First round - builds cache + df1 = strategy.advise_indicators(ohlcv_history, {"pair": pair}) assert len(df1) == len(ohlcv_history) assert "open" in df1.columns for col in expected_cols: @@ -541,8 +543,10 @@ def test_analyze_with_orderflow( if col not in ("stacked_imbalances_bid", "stacked_imbalances_ask"): assert df1[col].count() == 5, f"Column {col} has {df1[col].count()} non-NaN values" + assert len(strategy._cached_grouped_trades_per_pair[pair]) == 5 + # Ensure caching works - call the same logic again. - df2 = strategy.advise_indicators(ohlcv_history, {"pair": "ETH/BTC"}) + df2 = strategy.advise_indicators(ohlcv_history, {"pair": pair}) assert len(df2) == len(ohlcv_history) assert "open" in df2.columns for col in expected_cols: From 6584f86bce6f8cb4064bcdc8fa8ae74053bd880b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 18:25:53 +0100 Subject: [PATCH 34/92] test: add Spy to improve test --- tests/data/test_converter_orderflow.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index 42398ead996..c73706bedd0 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -499,11 +499,15 @@ def test_analyze_with_orderflow( strategy.dp = DataProvider(default_conf_usdt, None, None) mocker.patch.object(strategy.dp, "trades", return_value=populate_dataframe_with_trades_trades) + import freqtrade.data.converter.orderflow as orderflow_module - df = strategy.advise_indicators(ohlcv_history, {"pair:": "ETH/BTC"}) + spy = mocker.spy(orderflow_module, "trades_to_volumeprofile_with_total_delta_bid_ask") + + pair = "ETH/BTC" + df = strategy.advise_indicators(ohlcv_history, {"pair:": pair}) assert len(df) == len(ohlcv_history) assert "open" in df.columns - pair = "ETH/BTC" + assert spy.call_count == 0 expected_cols = [ "trades", @@ -537,6 +541,8 @@ def test_analyze_with_orderflow( df1 = strategy.advise_indicators(ohlcv_history, {"pair": pair}) assert len(df1) == len(ohlcv_history) assert "open" in df1.columns + assert spy.call_count == 5 + for col in expected_cols: assert col in df1.columns, f"Column {col} not found in df.columns" @@ -545,10 +551,16 @@ def test_analyze_with_orderflow( assert len(strategy._cached_grouped_trades_per_pair[pair]) == 5 + lastval_trades = df1.at[len(df1) - 1, "trades"] + assert isinstance(lastval_trades, list) + assert len(lastval_trades) == 122 + + spy.reset_mock() # Ensure caching works - call the same logic again. df2 = strategy.advise_indicators(ohlcv_history, {"pair": pair}) assert len(df2) == len(ohlcv_history) assert "open" in df2.columns + assert spy.call_count == 0 for col in expected_cols: assert col in df2.columns, f"Round2: Column {col} not found in df.columns" From 4879582896408457660393e0f6b75c68a2dde6dc Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 19:31:34 +0100 Subject: [PATCH 35/92] feat: use dataframe.at directly to avoid intermediate series --- freqtrade/data/converter/orderflow.py | 63 ++++++++++----------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 364f979875c..a3065c41459 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -93,13 +93,6 @@ def populate_dataframe_with_trades( trades = trades.loc[trades["candle_start"] >= start_date] trades.reset_index(inplace=True, drop=True) - # Create Series to hold complex data - trades_series = pd.Series(index=dataframe.index, dtype=object) - orderflow_series = pd.Series(index=dataframe.index, dtype=object) - imbalances_series = pd.Series(index=dataframe.index, dtype=object) - stacked_imbalances_bid_series = pd.Series(index=dataframe.index, dtype=object) - stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object) - # group trades by candle start trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) @@ -126,37 +119,34 @@ def populate_dataframe_with_trades( ) continue - indices = dataframe.index[is_between].tolist() - # Add trades to each candle - trades_series.loc[indices] = [ - trades_grouped_df.drop(columns=["candle_start", "candle_end"]).to_dict( - orient="records" - ) - ] + # there can only be one row with the same date + index = dataframe.index[is_between][0] + dataframe.at[index, "trades"] = trades_grouped_df.drop( + columns=["candle_start", "candle_end"] + ).to_dict(orient="records") + # Calculate orderflow for each candle orderflow = trades_to_volumeprofile_with_total_delta_bid_ask( trades_grouped_df, scale=config_orderflow["scale"] ) - orderflow_series.loc[indices] = [orderflow.to_dict(orient="index")] + dataframe.at[index, "orderflow"] = orderflow.to_dict(orient="index") + # orderflow_series.loc[[index]] = [orderflow.to_dict(orient="index")] # Calculate imbalances for each candle's orderflow imbalances = trades_orderflow_to_imbalances( orderflow, imbalance_ratio=config_orderflow["imbalance_ratio"], imbalance_volume=config_orderflow["imbalance_volume"], ) - imbalances_series.loc[indices] = [imbalances.to_dict(orient="index")] + dataframe.at[index, "imbalances"] = imbalances.to_dict(orient="index") stacked_imbalance_range = config_orderflow["stacked_imbalance_range"] - stacked_imbalances_bid_series.loc[indices] = [ - stacked_imbalance_bid( - imbalances, stacked_imbalance_range=stacked_imbalance_range - ) - ] - stacked_imbalances_ask_series.loc[indices] = [ - stacked_imbalance_ask( - imbalances, stacked_imbalance_range=stacked_imbalance_range - ) - ] + dataframe.at[index, "stacked_imbalances_bid"] = stacked_imbalance_bid( + imbalances, stacked_imbalance_range=stacked_imbalance_range + ) + + dataframe.at[index, "stacked_imbalances_ask"] = stacked_imbalance_ask( + imbalances, stacked_imbalance_range=stacked_imbalance_range + ) bid = np.where( trades_grouped_df["side"].str.contains("sell"), trades_grouped_df["amount"], 0 @@ -168,15 +158,15 @@ def populate_dataframe_with_trades( deltas_per_trade = ask - bid min_delta = deltas_per_trade.cumsum().min() max_delta = deltas_per_trade.cumsum().max() - dataframe.loc[indices, "max_delta"] = max_delta - dataframe.loc[indices, "min_delta"] = min_delta + dataframe.loc[index, "max_delta"] = max_delta + dataframe.loc[index, "min_delta"] = min_delta - dataframe.loc[indices, "bid"] = bid.sum() - dataframe.loc[indices, "ask"] = ask.sum() - dataframe.loc[indices, "delta"] = ( - dataframe.loc[indices, "ask"] - dataframe.loc[indices, "bid"] + dataframe.loc[index, "bid"] = bid.sum() + dataframe.loc[index, "ask"] = ask.sum() + dataframe.loc[index, "delta"] = ( + dataframe.loc[index, "ask"] - dataframe.loc[index, "bid"] ) - dataframe.loc[indices, "total_trades"] = len(trades_grouped_df) + dataframe.loc[index, "total_trades"] = len(trades_grouped_df) # Cache the result cached_grouped_trades[(candle_start, candle_next)] = dataframe.loc[ @@ -193,13 +183,6 @@ def populate_dataframe_with_trades( logger.debug(f"Found NO candles for trades starting with {candle_start}") logger.debug(f"trades.groups_keys in {time.time() - start_time} seconds") - # Merge the complex data Series back into the DataFrame - dataframe["trades"] = trades_series - dataframe["orderflow"] = orderflow_series - dataframe["imbalances"] = imbalances_series - dataframe["stacked_imbalances_bid"] = stacked_imbalances_bid_series - dataframe["stacked_imbalances_ask"] = stacked_imbalances_ask_series - except Exception as e: logger.exception("Error populating dataframe with trades") raise DependencyException(e) From 48dd86bd9b4c012327318dca72c566451ca6b3ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 19:33:49 +0100 Subject: [PATCH 36/92] chore: use `.at` for assignments --- freqtrade/data/converter/orderflow.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index a3065c41459..233923ab99b 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -158,15 +158,15 @@ def populate_dataframe_with_trades( deltas_per_trade = ask - bid min_delta = deltas_per_trade.cumsum().min() max_delta = deltas_per_trade.cumsum().max() - dataframe.loc[index, "max_delta"] = max_delta - dataframe.loc[index, "min_delta"] = min_delta + dataframe.at[index, "max_delta"] = max_delta + dataframe.at[index, "min_delta"] = min_delta - dataframe.loc[index, "bid"] = bid.sum() - dataframe.loc[index, "ask"] = ask.sum() - dataframe.loc[index, "delta"] = ( - dataframe.loc[index, "ask"] - dataframe.loc[index, "bid"] + dataframe.at[index, "bid"] = bid.sum() + dataframe.at[index, "ask"] = ask.sum() + dataframe.at[index, "delta"] = ( + dataframe.at[index, "ask"] - dataframe.at[index, "bid"] ) - dataframe.loc[index, "total_trades"] = len(trades_grouped_df) + dataframe.at[index, "total_trades"] = len(trades_grouped_df) # Cache the result cached_grouped_trades[(candle_start, candle_next)] = dataframe.loc[ From 9d07f5dc2e3d662355bdbb533ef4811e72f44a40 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Dec 2024 19:37:36 +0100 Subject: [PATCH 37/92] chore: reduce orderflow code duplication --- freqtrade/data/converter/orderflow.py | 44 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 233923ab99b..ede5690c07f 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -17,6 +17,20 @@ logger = logging.getLogger(__name__) +ADDED_COLUMNS = [ + "trades", + "orderflow", + "imbalances", + "stacked_imbalances_bid", + "stacked_imbalances_ask", + "max_delta", + "min_delta", + "bid", + "ask", + "delta", + "total_trades", +] + def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame): """ @@ -24,24 +38,18 @@ def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame): :param dataframe: Dataframe to populate """ # Initialize columns with appropriate dtypes - dataframe["trades"] = np.nan - dataframe["orderflow"] = np.nan - dataframe["imbalances"] = np.nan - dataframe["stacked_imbalances_bid"] = np.nan - dataframe["stacked_imbalances_ask"] = np.nan - dataframe["max_delta"] = np.nan - dataframe["min_delta"] = np.nan - dataframe["bid"] = np.nan - dataframe["ask"] = np.nan - dataframe["delta"] = np.nan - dataframe["total_trades"] = np.nan - - # Ensure the 'trades' column is of object type - dataframe["trades"] = dataframe["trades"].astype(object) - dataframe["orderflow"] = dataframe["orderflow"].astype(object) - dataframe["imbalances"] = dataframe["imbalances"].astype(object) - dataframe["stacked_imbalances_bid"] = dataframe["stacked_imbalances_bid"].astype(object) - dataframe["stacked_imbalances_ask"] = dataframe["stacked_imbalances_ask"].astype(object) + for column in ADDED_COLUMNS: + dataframe[column] = np.nan + + # Set columns to object type + for column in ( + "trades", + "orderflow", + "imbalances", + "stacked_imbalances_bid", + "stacked_imbalances_ask", + ): + dataframe[column] = dataframe[column].astype(object) def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str): From 621dfc136ed6b45bf4c542e281632d2066e3b0fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 06:38:49 +0100 Subject: [PATCH 38/92] fix: Improved caching closes #11008 --- freqtrade/data/converter/orderflow.py | 54 +++++++++------------------ freqtrade/strategy/interface.py | 8 +--- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index ede5690c07f..c3a6225225e 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -68,18 +68,17 @@ def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str): def populate_dataframe_with_trades( - cached_grouped_trades: OrderedDict[tuple[datetime, datetime], pd.DataFrame], + cached_grouped_trades: pd.DataFrame | None, config: Config, dataframe: pd.DataFrame, trades: pd.DataFrame, -) -> tuple[pd.DataFrame, OrderedDict[tuple[datetime, datetime], pd.DataFrame]]: +) -> tuple[pd.DataFrame, pd.DataFrame]: """ Populates a dataframe with trades :param dataframe: Dataframe to populate :param trades: Trades to populate with :return: Dataframe with trades populated """ - from freqtrade.exchange import timeframe_to_next_date timeframe = config["timeframe"] config_orderflow = config["orderflow"] @@ -101,34 +100,27 @@ def populate_dataframe_with_trades( trades = trades.loc[trades["candle_start"] >= start_date] trades.reset_index(inplace=True, drop=True) - # group trades by candle start trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) candle_start: datetime for candle_start, trades_grouped_df in trades_grouped_by_candle_start: is_between = candle_start == dataframe["date"] if is_between.any(): - candle_next = timeframe_to_next_date(timeframe, candle_start) - if candle_next not in trades_grouped_by_candle_start.groups: - logger.warning( - f"candle at {candle_start} with {len(trades_grouped_df)} trades " - f"might be unfinished, because no finished trades at {candle_next}" - ) - - # Use caching mechanism - if (candle_start, candle_next) in cached_grouped_trades: - cache_entry = cached_grouped_trades[(candle_start, candle_next)] - # dataframe.loc[is_between] = cache_entry # doesn't take, so we need workaround: - # Create a dictionary of the column values to be assigned - update_dict = {c: cache_entry[c].iat[0] for c in cache_entry.columns} - # Assign the values using the update_dict - dataframe.loc[is_between, update_dict.keys()] = pd.DataFrame( - [update_dict], index=dataframe.loc[is_between].index - ) - continue - # there can only be one row with the same date index = dataframe.index[is_between][0] + + if ( + cached_grouped_trades is not None + and (candle_start == cached_grouped_trades["date"]).any() + ): + logger.info(f"Using cached orderflow data for {candle_start}") + # Check if the trades are already in the cache + for col in ADDED_COLUMNS: + dataframe.at[index, col] = cached_grouped_trades.loc[ + (cached_grouped_trades["date"] == candle_start), col + ].values + continue + dataframe.at[index, "trades"] = trades_grouped_df.drop( columns=["candle_start", "candle_end"] ).to_dict(orient="records") @@ -176,21 +168,11 @@ def populate_dataframe_with_trades( ) dataframe.at[index, "total_trades"] = len(trades_grouped_df) - # Cache the result - cached_grouped_trades[(candle_start, candle_next)] = dataframe.loc[ - is_between - ].copy() - - # Maintain cache size - if ( - config.get("runmode") in (RunMode.DRY_RUN, RunMode.LIVE) - and len(cached_grouped_trades) > config_orderflow["cache_size"] - ): - cached_grouped_trades.popitem(last=False) - else: - logger.debug(f"Found NO candles for trades starting with {candle_start}") logger.debug(f"trades.groups_keys in {time.time() - start_time} seconds") + # Cache the entire dataframe + cached_grouped_trades = dataframe.tail(config_orderflow["cache_size"]).copy() + except Exception as e: logger.exception("Error populating dataframe with trades") raise DependencyException(e) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1e415e9fcec..1babfdcd6d3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -141,9 +141,7 @@ class IStrategy(ABC, HyperStrategyMixin): market_direction: MarketDirection = MarketDirection.NONE # Global cache dictionary - _cached_grouped_trades_per_pair: dict[ - str, OrderedDict[tuple[datetime, datetime], DataFrame] - ] = {} + _cached_grouped_trades_per_pair: dict[str, DataFrame] = {} def __init__(self, config: Config) -> None: self.config = config @@ -1608,9 +1606,7 @@ def _if_enabled_populate_trades(self, dataframe: DataFrame, metadata: dict): config["timeframe"] = self.timeframe pair = metadata["pair"] # TODO: slice trades to size of dataframe for faster backtesting - cached_grouped_trades: OrderedDict[tuple[datetime, datetime], DataFrame] = ( - self._cached_grouped_trades_per_pair.get(pair, OrderedDict()) - ) + cached_grouped_trades: DataFrame | None = self._cached_grouped_trades_per_pair.get(pair) dataframe, cached_grouped_trades = populate_dataframe_with_trades( cached_grouped_trades, config, dataframe, trades ) From 6ab528748ec3e007b30b7268966758c7e2dab76f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 06:40:15 +0100 Subject: [PATCH 39/92] tests: Update orderflow tests --- freqtrade/data/converter/orderflow.py | 2 -- freqtrade/strategy/interface.py | 1 - tests/data/test_converter_orderflow.py | 10 +++------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index c3a6225225e..993b5eebcfe 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -4,14 +4,12 @@ import logging import time -from collections import OrderedDict from datetime import datetime import numpy as np import pandas as pd from freqtrade.constants import DEFAULT_ORDERFLOW_COLUMNS, Config -from freqtrade.enums import RunMode from freqtrade.exceptions import DependencyException diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1babfdcd6d3..24de4252cdc 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -5,7 +5,6 @@ import logging from abc import ABC, abstractmethod -from collections import OrderedDict from datetime import datetime, timedelta, timezone from math import isinf, isnan diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index c73706bedd0..ded6d208814 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import numpy as np import pandas as pd import pytest @@ -105,7 +103,7 @@ def test_public_trades_mock_populate_dataframe_with_trades__check_orderflow( }, } # Apply the function to populate the data frame with order flow data - df, _ = populate_dataframe_with_trades(OrderedDict(), config, dataframe, trades) + df, _ = populate_dataframe_with_trades(None, config, dataframe, trades) # Extract results from the first row of the DataFrame results = df.iloc[0] t = results["trades"] @@ -246,7 +244,7 @@ def test_public_trades_trades_mock_populate_dataframe_with_trades__check_trades( } # Populate the DataFrame with trades and order flow data - df, _ = populate_dataframe_with_trades(OrderedDict(), config, dataframe, trades) + df, _ = populate_dataframe_with_trades(None, config, dataframe, trades) # --- DataFrame and Trade Data Validation --- @@ -404,9 +402,7 @@ def test_public_trades_config_max_trades( }, } - df, _ = populate_dataframe_with_trades( - OrderedDict(), default_conf | orderflow_config, dataframe, trades - ) + df, _ = populate_dataframe_with_trades(None, default_conf | orderflow_config, dataframe, trades) assert df.delta.count() == 1 From 3137d7cf2cdd39d95782bf7b1298008f3a399b8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 06:43:01 +0100 Subject: [PATCH 40/92] chore: remove unnecessary config alias --- freqtrade/strategy/interface.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 24de4252cdc..48f77136e7c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1601,13 +1601,11 @@ def _if_enabled_populate_trades(self, dataframe: DataFrame, metadata: dict): if use_public_trades: trades = self.dp.trades(pair=metadata["pair"], copy=False) - config = self.config - config["timeframe"] = self.timeframe pair = metadata["pair"] # TODO: slice trades to size of dataframe for faster backtesting cached_grouped_trades: DataFrame | None = self._cached_grouped_trades_per_pair.get(pair) dataframe, cached_grouped_trades = populate_dataframe_with_trades( - cached_grouped_trades, config, dataframe, trades + cached_grouped_trades, self.config, dataframe, trades ) # dereference old cache From 171157c100da88b137051b8e9991a63089d537f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 07:03:24 +0100 Subject: [PATCH 41/92] chore: further simplify orderflow code --- freqtrade/data/converter/orderflow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 993b5eebcfe..fc016664e89 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -154,10 +154,8 @@ def populate_dataframe_with_trades( trades_grouped_df["side"].str.contains("buy"), trades_grouped_df["amount"], 0 ) deltas_per_trade = ask - bid - min_delta = deltas_per_trade.cumsum().min() - max_delta = deltas_per_trade.cumsum().max() - dataframe.at[index, "max_delta"] = max_delta - dataframe.at[index, "min_delta"] = min_delta + dataframe.at[index, "max_delta"] = deltas_per_trade.cumsum().max() + dataframe.at[index, "min_delta"] = deltas_per_trade.cumsum().min() dataframe.at[index, "bid"] = bid.sum() dataframe.at[index, "ask"] = ask.sum() From 6c25feabf26ccdd240dca04fc422f1b06feeedf6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 20:04:59 +0100 Subject: [PATCH 42/92] tests: assert type of orderflow object --- tests/data/test_converter_orderflow.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index ded6d208814..3bf7faf58cb 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -551,6 +551,9 @@ def test_analyze_with_orderflow( assert isinstance(lastval_trades, list) assert len(lastval_trades) == 122 + lastval_of = df1.at[len(df1) - 1, "orderflow"] + assert isinstance(lastval_of, dict) + spy.reset_mock() # Ensure caching works - call the same logic again. df2 = strategy.advise_indicators(ohlcv_history, {"pair": pair}) @@ -564,3 +567,10 @@ def test_analyze_with_orderflow( assert ( df2[col].count() == 5 ), f"Round2: Column {col} has {df2[col].count()} non-NaN values" + + lastval_trade2 = df2.at[len(df2) - 1, "trades"] + assert isinstance(lastval_trade2, list) + assert len(lastval_trade2) == 122 + + lastval_of2 = df2.at[len(df2) - 1, "orderflow"] + assert isinstance(lastval_of2, dict) From ab39ac29e80c23d4e2128dedd7b193c88527d606 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 20:05:33 +0100 Subject: [PATCH 43/92] fix: ensure data type is maintained when data comes from cache. --- freqtrade/data/converter/orderflow.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index fc016664e89..877325e5e14 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -113,10 +113,11 @@ def populate_dataframe_with_trades( ): logger.info(f"Using cached orderflow data for {candle_start}") # Check if the trades are already in the cache + cache_idx = cached_grouped_trades.index[ + cached_grouped_trades["date"] == candle_start + ][0] for col in ADDED_COLUMNS: - dataframe.at[index, col] = cached_grouped_trades.loc[ - (cached_grouped_trades["date"] == candle_start), col - ].values + dataframe.at[index, col] = cached_grouped_trades.at[cache_idx, col] continue dataframe.at[index, "trades"] = trades_grouped_df.drop( From 267d9333a18287e2d31feb32c6f8b296ae9277dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 20:09:07 +0100 Subject: [PATCH 44/92] chore: remove pointless, very noisy log message. --- freqtrade/data/converter/orderflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 877325e5e14..d82cccdb54a 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -111,7 +111,6 @@ def populate_dataframe_with_trades( cached_grouped_trades is not None and (candle_start == cached_grouped_trades["date"]).any() ): - logger.info(f"Using cached orderflow data for {candle_start}") # Check if the trades are already in the cache cache_idx = cached_grouped_trades.index[ cached_grouped_trades["date"] == candle_start From e09d1b42f27b7484f0f6dd3abe8734b805ede34f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Dec 2024 19:56:26 +0100 Subject: [PATCH 45/92] chore: eliminate duplicate trades data grouping --- freqtrade/data/converter/orderflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index 0bfd7a05fe1..52256e7b49a 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -91,8 +91,6 @@ def populate_dataframe_with_trades( trades = trades.loc[trades["candle_start"] >= start_date] trades.reset_index(inplace=True, drop=True) - # group trades by candle start - trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) # Create Series to hold complex data trades_series = pd.Series(index=dataframe.index, dtype=object) orderflow_series = pd.Series(index=dataframe.index, dtype=object) @@ -100,7 +98,9 @@ def populate_dataframe_with_trades( stacked_imbalances_bid_series = pd.Series(index=dataframe.index, dtype=object) stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object) + # group trades by candle start trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False) + candle_start: datetime for candle_start, trades_grouped_df in trades_grouped_by_candle_start: is_between = candle_start == dataframe["date"] From 9f5cb3a07e65f8cd814c8b318684c1170b630f0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 20:11:09 +0100 Subject: [PATCH 46/92] chore: add exif-stripper to ensure exif data is removed --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3504f3b68d3..6f3d2ff27a1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,6 +56,11 @@ repos: .*\.md )$ + - repo: https://github.com/stefmolin/exif-stripper + rev: 0.6.1 + hooks: + - id: strip-exif + - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: From 63f8217cd5ad6a105ff3e852eff9f97dc0ebe533 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 6 Dec 2024 20:11:46 +0100 Subject: [PATCH 47/92] chore: ensure exif-data is removed from images --- docs/assets/frequi-login-CORS-light.png | Bin 54458 -> 52311 bytes docs/assets/telegram_forcebuy.png | Bin 17895 -> 12035 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/assets/frequi-login-CORS-light.png b/docs/assets/frequi-login-CORS-light.png index 019ae4e8c90d768c050d0ba6308d40ab4c819c3b..cf6d7a700f73d3b72cdb5a97def3d2279acd88d8 100644 GIT binary patch literal 52311 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 tQDmsq~(e8}) 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 ZgSKN$zdSZA{0aqmNQuddRtOvX{x1OmZ&UyP literal 54458 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 Date: Fri, 6 Dec 2024 20:16:46 +0100 Subject: [PATCH 48/92] chore: improve naming, don't duplicate column list --- freqtrade/data/converter/orderflow.py | 6 +++--- tests/data/test_converter_orderflow.py | 24 +++++++----------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index d82cccdb54a..777af17dc8b 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) -ADDED_COLUMNS = [ +ORDERFLOW_ADDED_COLUMNS = [ "trades", "orderflow", "imbalances", @@ -36,7 +36,7 @@ def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame): :param dataframe: Dataframe to populate """ # Initialize columns with appropriate dtypes - for column in ADDED_COLUMNS: + for column in ORDERFLOW_ADDED_COLUMNS: dataframe[column] = np.nan # Set columns to object type @@ -115,7 +115,7 @@ def populate_dataframe_with_trades( cache_idx = cached_grouped_trades.index[ cached_grouped_trades["date"] == candle_start ][0] - for col in ADDED_COLUMNS: + for col in ORDERFLOW_ADDED_COLUMNS: dataframe.at[index, col] = cached_grouped_trades.at[cache_idx, col] continue diff --git a/tests/data/test_converter_orderflow.py b/tests/data/test_converter_orderflow.py index 3bf7faf58cb..96f550020ab 100644 --- a/tests/data/test_converter_orderflow.py +++ b/tests/data/test_converter_orderflow.py @@ -4,7 +4,10 @@ from freqtrade.constants import DEFAULT_TRADES_COLUMNS from freqtrade.data.converter import populate_dataframe_with_trades -from freqtrade.data.converter.orderflow import trades_to_volumeprofile_with_total_delta_bid_ask +from freqtrade.data.converter.orderflow import ( + ORDERFLOW_ADDED_COLUMNS, + trades_to_volumeprofile_with_total_delta_bid_ask, +) from freqtrade.data.converter.trade_converter import trades_list_to_df from freqtrade.data.dataprovider import DataProvider from tests.strategy.strats.strategy_test_v3 import StrategyTestV3 @@ -505,21 +508,8 @@ def test_analyze_with_orderflow( assert "open" in df.columns assert spy.call_count == 0 - expected_cols = [ - "trades", - "orderflow", - "imbalances", - "stacked_imbalances_bid", - "stacked_imbalances_ask", - "max_delta", - "min_delta", - "bid", - "ask", - "delta", - "total_trades", - ] # Not expected to run - shouldn't have added orderflow columns - for col in expected_cols: + for col in ORDERFLOW_ADDED_COLUMNS: assert col not in df.columns, f"Column {col} found in df.columns" default_conf_usdt["exchange"]["use_public_trades"] = True @@ -539,7 +529,7 @@ def test_analyze_with_orderflow( assert "open" in df1.columns assert spy.call_count == 5 - for col in expected_cols: + for col in ORDERFLOW_ADDED_COLUMNS: assert col in df1.columns, f"Column {col} not found in df.columns" if col not in ("stacked_imbalances_bid", "stacked_imbalances_ask"): @@ -560,7 +550,7 @@ def test_analyze_with_orderflow( assert len(df2) == len(ohlcv_history) assert "open" in df2.columns assert spy.call_count == 0 - for col in expected_cols: + for col in ORDERFLOW_ADDED_COLUMNS: assert col in df2.columns, f"Round2: Column {col} not found in df.columns" if col not in ("stacked_imbalances_bid", "stacked_imbalances_ask"): From fb9e11b7b52f46e310e8338a9811b6d3692bf3ae Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2024 13:21:18 +0100 Subject: [PATCH 49/92] chore: improve type safety --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1e415e9fcec..1a032f3a22b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1163,10 +1163,10 @@ def get_latest_candle( logger.warning(f"Empty candle (OHLCV) data for pair {pair}") return None, None - latest_date = dataframe["date"].max() - latest = dataframe.loc[dataframe["date"] == latest_date].iloc[-1] + latest_date_pd = dataframe["date"].max() + latest = dataframe.loc[dataframe["date"] == latest_date_pd].iloc[-1] # Explicitly convert to datetime object to ensure the below comparison does not fail - latest_date = latest_date.to_pydatetime() + latest_date: datetime = latest_date_pd.to_pydatetime() # Check if dataframe is out of date timeframe_minutes = timeframe_to_minutes(timeframe) From 98e0a5f10101c365cac05311317e9018fcf6aaf6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2024 15:51:37 +0100 Subject: [PATCH 50/92] chore: remove unused arguments in loss functions --- freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py | 3 --- .../hyperopt_loss/hyperopt_loss_max_drawdown_relative.py | 3 +-- freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 2 -- .../optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py | 3 +-- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py index 2a04d40707d..4f1a82e1ea4 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py @@ -9,7 +9,6 @@ from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_calmar from freqtrade.optimize.hyperopt import IHyperOptLoss @@ -24,10 +23,8 @@ class CalmarHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( results: DataFrame, - trade_count: int, min_date: datetime, max_date: datetime, - config: Config, starting_balance: float, *args, **kwargs, diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py index a753263a3ab..3cd578cb403 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py @@ -7,7 +7,6 @@ from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_underwater from freqtrade.optimize.hyperopt import IHyperOptLoss @@ -22,7 +21,7 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( - results: DataFrame, config: Config, starting_balance: float, *args, **kwargs + results: DataFrame, starting_balance: float, *args, **kwargs ) -> float: """ Objective function. diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 7a10d279b8b..adffdfb0bca 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -33,7 +33,6 @@ import numpy as np from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss @@ -57,7 +56,6 @@ class MultiMetricHyperOptLoss(IHyperOptLoss): def hyperopt_loss_function( results: DataFrame, trade_count: int, - config: Config, starting_balance: float, **kwargs, ) -> float: diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py index 2ab581520a8..af1b33dfb7e 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py @@ -10,7 +10,6 @@ from pandas import DataFrame -from freqtrade.constants import Config from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss @@ -22,7 +21,7 @@ class ProfitDrawDownHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( - results: DataFrame, config: Config, starting_balance: float, *args, **kwargs + results: DataFrame, starting_balance: float, *args, **kwargs ) -> float: total_profit = results["profit_abs"].sum() From 0683ba3a54da1ec56252f732b680bf27f58823c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 08:30:45 +0100 Subject: [PATCH 51/92] feat: limit environment-variable json parsing to lists --- freqtrade/configuration/environment_vars.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py index 5baa2bfc34c..8a825f59ba5 100644 --- a/freqtrade/configuration/environment_vars.py +++ b/freqtrade/configuration/environment_vars.py @@ -1,8 +1,9 @@ -import json import logging import os from typing import Any +import rapidjson + from freqtrade.constants import ENV_VAR_PREFIX from freqtrade.misc import deep_merge_dicts @@ -23,8 +24,11 @@ def _get_var_typed(val): return False # try to convert from json try: - return json.loads(val) - except json.decoder.JSONDecodeError: + value = rapidjson.loads(val) + # Limited to lists for now + if isinstance(value, list): + return value + except rapidjson.JSONDecodeError: pass # keep as string return val From 934bcf253eb4427ab8d372cb193e2346e49ca0ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 08:30:55 +0100 Subject: [PATCH 52/92] test: add tests for list parsing --- tests/test_configuration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 8011ded9638..3e9df382ba0 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1481,6 +1481,12 @@ def test_flat_vars_to_nested_dict(caplog): "FREQTRADE__STAKE_AMOUNT": "200.05", "FREQTRADE__TELEGRAM__CHAT_ID": "2151", "NOT_RELEVANT": "200.0", # Will be ignored + "FREQTRADE__ARRAY": '[{"name":"default","host":"xxx"}]', + "FREQTRADE__EXCHANGE__PAIR_WHITELIST": '["BTC/USDT", "ETH/USDT"]', + # Fails due to trailing comma + "FREQTRADE__ARRAY_TRAIL_COMMA": '[{"name":"default","host":"xxx",}]', + # Object fails + "FREQTRADE__OBJECT": '{"name":"default","host":"xxx"}', } expected = { "stake_amount": 200.05, @@ -1494,8 +1500,12 @@ def test_flat_vars_to_nested_dict(caplog): }, "some_setting": True, "some_false_setting": False, + "pair_whitelist": ["BTC/USDT", "ETH/USDT"], }, "telegram": {"chat_id": "2151"}, + "array": [{"name": "default", "host": "xxx"}], + "object": '{"name":"default","host":"xxx"}', + "array_trail_comma": '[{"name":"default","host":"xxx",}]', } res = _flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX) assert res == expected From 10b5d5e56bfb758db825cd5c2daadf3e0eaa4810 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 08:35:13 +0100 Subject: [PATCH 53/92] docs: add "list parsing" logic to documentation --- docs/configuration.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 16ae3760359..0228f6234c6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,13 +39,19 @@ Please note that Environment variables will overwrite corresponding settings in Common example: -``` +``` bash FREQTRADE__TELEGRAM__CHAT_ID= FREQTRADE__TELEGRAM__TOKEN= FREQTRADE__EXCHANGE__KEY= FREQTRADE__EXCHANGE__SECRET= ``` +Json lists are parsed as json - so you can use the following to set a list of pairs: + +``` bash +export FREQTRADE__EXCHANGE__PAIR_WHITELIST='["BTC/USDT", "ETH/USDT"]' +``` + !!! Note Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable. @@ -54,7 +60,7 @@ FREQTRADE__EXCHANGE__SECRET= ??? Warning "Loading sequence" Environment variables are loaded after the initial configuration. As such, you cannot provide the path to the configuration through environment variables. Please use `--config path/to/config.json` for that. - This also applies to user_dir to some degree. while the user directory can be set through environment variables - the configuration will **not** be loaded from that location. + This also applies to `user_dir` to some degree. while the user directory can be set through environment variables - the configuration will **not** be loaded from that location. ### Multiple configuration files From eee5d710e7c7b72fd2018be0033a20998e26958a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 09:59:40 +0100 Subject: [PATCH 54/92] chore: patch torch all the time - "list"tests do load the modules as well - so they need the same patch. --- tests/conftest.py | 25 +++++++++++++++++++++++++ tests/freqai/conftest.py | 25 ------------------------- tests/freqai/test_freqai_datakitchen.py | 3 +-- tests/freqai/test_freqai_interface.py | 11 ++++++++--- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5171708603e..df8a31974c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring import json import logging +import platform import re from copy import deepcopy from datetime import datetime, timedelta, timezone @@ -517,6 +518,30 @@ def patch_gc(mocker) -> None: mocker.patch("freqtrade.main.gc_set_threshold") +def is_arm() -> bool: + machine = platform.machine() + return "arm" in machine or "aarch64" in machine + + +def is_mac() -> bool: + machine = platform.system() + return "Darwin" in machine + + +@pytest.fixture(autouse=True) +def patch_torch_initlogs(mocker) -> None: + if is_mac(): + # Mock torch import completely + import sys + import types + + module_name = "torch" + mocked_module = types.ModuleType(module_name) + sys.modules[module_name] = mocked_module + else: + mocker.patch("torch._logging._init_logs") + + @pytest.fixture(autouse=True) def user_dir(mocker, tmp_path) -> Path: user_dir = tmp_path / "user_data" diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index 442f530203a..9e14111af36 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -1,4 +1,3 @@ -import platform import sys from copy import deepcopy from pathlib import Path @@ -20,30 +19,6 @@ def is_py12() -> bool: return sys.version_info >= (3, 12) -def is_mac() -> bool: - machine = platform.system() - return "Darwin" in machine - - -def is_arm() -> bool: - machine = platform.machine() - return "arm" in machine or "aarch64" in machine - - -@pytest.fixture(autouse=True) -def patch_torch_initlogs(mocker) -> None: - if is_mac(): - # Mock torch import completely - import sys - import types - - module_name = "torch" - mocked_module = types.ModuleType(module_name) - sys.modules[module_name] = mocked_module - else: - mocker.patch("torch._logging._init_logs") - - @pytest.fixture(scope="function") def freqai_conf(default_conf, tmp_path): freqaiconf = deepcopy(default_conf) diff --git a/tests/freqai/test_freqai_datakitchen.py b/tests/freqai/test_freqai_datakitchen.py index 27efc3a662e..7a219e46ecc 100644 --- a/tests/freqai/test_freqai_datakitchen.py +++ b/tests/freqai/test_freqai_datakitchen.py @@ -10,11 +10,10 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException from freqtrade.freqai.data_kitchen import FreqaiDataKitchen -from tests.conftest import get_patched_exchange +from tests.conftest import get_patched_exchange, is_mac from tests.freqai.conftest import ( get_patched_data_kitchen, get_patched_freqai_strategy, - is_mac, make_unfiltered_dataframe, ) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 2779ddcb8a3..0710591c926 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -13,11 +13,16 @@ from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import Trade from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re -from tests.freqai.conftest import ( - get_patched_freqai_strategy, +from tests.conftest import ( + EXMS, + create_mock_trades, + get_patched_exchange, is_arm, is_mac, + log_has_re, +) +from tests.freqai.conftest import ( + get_patched_freqai_strategy, make_rl_config, mock_pytorch_mlp_model_training_parameters, ) From 8dc322d7f2c8717a1f5b102996a25ca396172633 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 10:20:21 +0100 Subject: [PATCH 55/92] chore: don't allow negative stake amounts --- freqtrade/wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 9864d060448..7fe78d93557 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -339,7 +339,7 @@ def _check_available_stake_amount(self, stake_amount: float, available_amount: f f"lower than stake amount ({stake_amount} {self._config['stake_currency']})" ) - return stake_amount + return max(stake_amount, 0) def get_trade_stake_amount( self, pair: str, max_open_trades: IntOrInf, edge=None, update: bool = True From 193b989342f86fff00a61b2ecb13b1f10b5e9da0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 11:44:35 +0100 Subject: [PATCH 56/92] chore: Be more precise with binance account limitation --- freqtrade/exchange/binance.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 6959811c316..74a37b4f6ad 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -91,7 +91,10 @@ def additional_exchange_init(self) -> None: "\nHedge Mode is not supported by freqtrade. " "Please change 'Position Mode' on your binance futures account." ) - if assets_margin.get("multiAssetsMargin") is True: + if ( + assets_margin.get("multiAssetsMargin") is True + and self.margin_mode != MarginMode.CROSS + ): msg += ( "\nMulti-Asset Mode is not supported by freqtrade. " "Please change 'Asset Mode' on your binance futures account." From 6d698e584bf4501c5e7bc3e8cafada8850bcdd1b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 11:45:25 +0100 Subject: [PATCH 57/92] chore: return 0 total balances total may be 0, while still having "free" balance (cross futures scenarios) --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d4c05d2d23d..bc4f84f24f8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -726,7 +726,7 @@ def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> dict: coin: str balance: Wallet for coin, balance in self._freqtrade.wallets.get_all_balances().items(): - if not balance.total: + if not balance.total and not balance.free: continue trade = open_assets.get(coin, None) From 3c88bdc60c30fbbad70090172db1c5b366b97ca5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 11:55:52 +0100 Subject: [PATCH 58/92] fix: round liquidation price to price precision --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 54f37d00d35..9e18378c1ac 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -3599,7 +3599,7 @@ def get_liquidation_price( liquidation_price_buffer = ( liquidation_price - buffer_amount if is_short else liquidation_price + buffer_amount ) - return max(liquidation_price_buffer, 0.0) + return self.price_to_precision(pair, max(liquidation_price_buffer, 0.0)) else: return None From ae1baf5789afafbb85e699c7bdf2871b0b95a5c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 13:07:34 +0100 Subject: [PATCH 59/92] test: mock price_to_precision for liquidation calculations --- tests/exchange/test_binance.py | 1 + tests/exchange/test_exchange.py | 1 + tests/optimize/test_backtesting.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index cc28760fa1f..3cd66807483 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -293,6 +293,7 @@ def test_liquidation_price_binance( default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = margin_mode default_conf["liquidation_buffer"] = 0.0 + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) exchange = get_patched_exchange(mocker, default_conf, exchange="binance") def get_maint_ratio(pair_, stake_amount): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 9d129601cc3..af95e57485e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -6131,6 +6131,7 @@ def test_get_liquidation_price( default_conf_usdt["exchange"]["name"] = exchange_name default_conf_usdt["margin_mode"] = margin_mode mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 05ebf229899..1b9945cb5fb 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -568,6 +568,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) mocker.patch(f"{EXMS}.get_max_leverage", return_value=100) mocker.patch("freqtrade.optimize.backtesting.price_to_precision", lambda p, *args: p) patch_exchange(mocker) @@ -1842,6 +1843,7 @@ def _trend_alternate_hold(dataframe=None, metadata=None): if use_detail: default_conf_usdt["timeframe_detail"] = "1m" + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) mocker.patch(f"{EXMS}.get_fee", fee) From 654d2ab63db47453f3f2e18bba022fb0f9949d91 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 13:45:03 +0100 Subject: [PATCH 60/92] fix: Round stoploss_dist to price_precision --- freqtrade/rpc/rpc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index bc4f84f24f8..5d8b6a2e6ee 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -33,6 +33,7 @@ from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.exchange.exchange_types import Ticker, Tickers +from freqtrade.exchange.exchange_utils import price_to_precision from freqtrade.loggers import bufferHandler from freqtrade.persistence import KeyStoreKeys, KeyValueStore, PairLocks, Trade from freqtrade.persistence.models import PairLock @@ -243,7 +244,11 @@ def _rpc_trade_status(self, trade_ids: list[int] | None = None) -> list[dict[str stoploss_entry_dist_ratio = stop_entry.profit_ratio # calculate distance to stoploss - stoploss_current_dist = trade.stop_loss - current_rate + stoploss_current_dist = price_to_precision( + trade.stop_loss - current_rate, + trade.price_precision, + trade.precision_mode_price, + ) stoploss_current_dist_ratio = stoploss_current_dist / current_rate trade_dict = trade.to_json() From 142ea68dceb853d967bb04dec39d63acd1981487 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 14:22:36 +0100 Subject: [PATCH 61/92] chore: fix oddity of price_to_precision --- freqtrade/exchange/exchange_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 1c8a30001f4..1c989424248 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -308,7 +308,9 @@ def price_to_precision( decimal_to_precision( price, rounding_mode=rounding_mode, - precision=price_precision, + precision=int(price_precision) + if precisionMode != TICK_SIZE + else price_precision, counting_mode=precisionMode, ) ) From d0223e6f483f507e5897659ef30195164e265259 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 14:34:00 +0100 Subject: [PATCH 62/92] chore: simplify dry-run wallet update --- freqtrade/wallets.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 7fe78d93557..f53c262556f 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -130,18 +130,12 @@ def _update_dry(self) -> None: ) total_stake = current_stake + used_stake else: - tot_in_trades = 0 for position in open_trades: - # size = self._exchange._contracts_to_amount(position.pair, position['contracts']) - size = position.amount - collateral = position.stake_amount - leverage = position.leverage - tot_in_trades += collateral _positions[position.pair] = PositionWallet( position.pair, - position=size, - leverage=leverage, - collateral=collateral, + position=position.amount, + leverage=position.leverage, + collateral=position.stake_amount, side=position.trade_direction, ) current_stake = ( From 58357a074641d885dcfc521e18b58f27c96b422f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 14:37:10 +0100 Subject: [PATCH 63/92] chore: calculate total_stake only once --- freqtrade/wallets.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index f53c262556f..fb27d5e5d9a 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -124,11 +124,6 @@ def _update_dry(self) -> None: pending, trade.amount + curr_wallet_bal, ) - - current_stake = ( - self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades - ) - total_stake = current_stake + used_stake else: for position in open_trades: _positions[position.pair] = PositionWallet( @@ -138,12 +133,11 @@ def _update_dry(self) -> None: collateral=position.stake_amount, side=position.trade_direction, ) - current_stake = ( - self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades - ) used_stake = tot_in_trades - total_stake = current_stake + tot_in_trades + + current_stake = self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades + total_stake = current_stake + used_stake _wallets[self._stake_currency] = Wallet( currency=self._stake_currency, From 4cd8e6b4444bcabb3833206d24aeb6a4fdae933c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 15:22:40 +0100 Subject: [PATCH 64/92] chore: simplify rpc_balance method by relying on exchange cache --- freqtrade/rpc/rpc.py | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5d8b6a2e6ee..a1a3df0db0a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -675,7 +675,7 @@ def _rpc_trade_statistics( } def __balance_get_est_stake( - self, coin: str, stake_currency: str, amount: float, balance: Wallet, tickers: Tickers + self, coin: str, stake_currency: str, amount: float, balance: Wallet ) -> tuple[float, float]: est_stake = 0.0 est_bot_stake = 0.0 @@ -686,25 +686,31 @@ def __balance_get_est_stake( est_stake = balance.free est_bot_stake = amount else: - pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - ticker: Ticker | None = tickers.get(pair, None) - if not ticker: - tickers_spot: Tickers = self._freqtrade.exchange.get_tickers( - cached=True, - market_type=TradingMode.SPOT - if self._config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT - else TradingMode.FUTURES, - ) - ticker = tickers_spot.get(pair, None) - - if ticker: - rate: float | None = ticker.get("last", None) - if rate: - if pair.startswith(stake_currency) and not pair.endswith(stake_currency): - rate = 1.0 / rate - est_stake = rate * balance.total - est_bot_stake = rate * amount - + try: + tickers: Tickers = self._freqtrade.exchange.get_tickers(cached=True) + pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) + ticker: Ticker | None = tickers.get(pair, None) + if not ticker: + tickers_spot: Tickers = self._freqtrade.exchange.get_tickers( + cached=True, + market_type=TradingMode.SPOT + if self._config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT + else TradingMode.FUTURES, + ) + ticker = tickers_spot.get(pair, None) + + if ticker: + rate: float | None = ticker.get("last", None) + if rate: + if pair.startswith(stake_currency) and not pair.endswith(stake_currency): + rate = 1.0 / rate + est_stake = rate * balance.total + est_bot_stake = rate * amount + + return est_stake, est_bot_stake + except (ExchangeError, PricingError) as e: + logger.warning(f"Error {e} getting rate for {coin}") + pass return est_stake, est_bot_stake def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> dict: @@ -712,10 +718,6 @@ def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> dict: currencies: list[dict] = [] total = 0.0 total_bot = 0.0 - try: - tickers: Tickers = self._freqtrade.exchange.get_tickers(cached=True) - except ExchangeError: - raise RPCException("Error getting current tickers.") open_trades: list[Trade] = Trade.get_open_trades() open_assets: dict[str, Trade] = {t.safe_base_currency: t for t in open_trades} @@ -742,7 +744,7 @@ def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> dict: try: est_stake, est_stake_bot = self.__balance_get_est_stake( - coin, stake_currency, trade_amount, balance, tickers + coin, stake_currency, trade_amount, balance ) except ValueError: continue From 0276e65f396f9047de0f1492c8e15b1487bb8b25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 15:29:23 +0100 Subject: [PATCH 65/92] test: update rpc test for new behavior --- tests/rpc/test_rpc.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index efdc18977e0..8fe59b5eace 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -514,8 +514,13 @@ def test_rpc_balance_handle_error(default_conf, mocker): patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter({}) - with pytest.raises(RPCException, match="Error getting current tickers."): - rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) + res = rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) + assert res["stake"] == "BTC" + + assert len(res["currencies"]) == 1 + assert res["currencies"][0]["currency"] == "BTC" + # ETH has not been converted. + assert all(currency["currency"] != "ETH" for currency in res["currencies"]) def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): @@ -597,10 +602,10 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): assert pytest.approx(result["total"]) == 2824.83464 assert pytest.approx(result["value"]) == 2824.83464 * 1.2 - assert tickers.call_count == 2 + assert tickers.call_count == 4 assert tickers.call_args_list[0][1]["cached"] is True # Testing futures - so we should get spot tickers - assert tickers.call_args_list[1][1]["market_type"] == "spot" + assert tickers.call_args_list[-1][1]["market_type"] == "spot" assert "USD" == result["symbol"] assert result["currencies"] == [ { From 2ff0abc6e7c5c5f516e63c58fdbfc2c54fe18699 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 15:44:46 +0100 Subject: [PATCH 66/92] refactor: conversation rate retrieval to exchange class for better reusability across the bot. --- freqtrade/exchange/exchange.py | 31 ++++++++++++++++++++++++++++++- freqtrade/rpc/rpc.py | 25 ++++++------------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9e18378c1ac..a0343147c31 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1855,7 +1855,36 @@ def get_tickers( except ccxt.BaseError as e: raise OperationalException(e) from e - # Pricing info + def get_conversion_rate(self, coin: str, currency: str) -> float: + """ + Quick and cached way to get conversion rate one currency to the other. + Can then be used as "rate * amount" to convert between currencies. + :param coin: Coin to convert + :param currency: Currency to convert to + :returns: Conversion rate from coin to currency + :raises: ExchangeErrors + """ + if coin == currency: + return 1.0 + tickers = self.get_tickers(cached=True) + pair = self.get_valid_pair_combination(coin, currency) + ticker: Ticker | None = tickers.get(pair, None) + if not ticker: + tickers_other: Tickers = self.get_tickers( + cached=True, + market_type=( + TradingMode.SPOT + if self.trading_mode != TradingMode.SPOT + else TradingMode.FUTURES + ), + ) + ticker = tickers_other.get(pair, None) + if ticker: + rate: float | None = ticker.get("last", None) + if rate and pair.startswith(currency) and not pair.endswith(currency): + rate = 1.0 / rate + return rate + return None @retrier def fetch_ticker(self, pair: str) -> Ticker: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a1a3df0db0a..3c9a12c93b7 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -687,25 +687,12 @@ def __balance_get_est_stake( est_bot_stake = amount else: try: - tickers: Tickers = self._freqtrade.exchange.get_tickers(cached=True) - pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - ticker: Ticker | None = tickers.get(pair, None) - if not ticker: - tickers_spot: Tickers = self._freqtrade.exchange.get_tickers( - cached=True, - market_type=TradingMode.SPOT - if self._config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT - else TradingMode.FUTURES, - ) - ticker = tickers_spot.get(pair, None) - - if ticker: - rate: float | None = ticker.get("last", None) - if rate: - if pair.startswith(stake_currency) and not pair.endswith(stake_currency): - rate = 1.0 / rate - est_stake = rate * balance.total - est_bot_stake = rate * amount + rate: float | None = self._freqtrade.exchange.get_conversion_rate( + coin, stake_currency + ) + if rate: + est_stake = rate * balance.total + est_bot_stake = rate * amount return est_stake, est_bot_stake except (ExchangeError, PricingError) as e: From d898c80e65761e1338491681ede0674a5cd04c96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 16:40:56 +0100 Subject: [PATCH 67/92] test: Add test for get_conversion_rate --- freqtrade/exchange/exchange.py | 6 ++++- tests/exchange/test_exchange.py | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a0343147c31..875cff6ee28 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1867,7 +1867,11 @@ def get_conversion_rate(self, coin: str, currency: str) -> float: if coin == currency: return 1.0 tickers = self.get_tickers(cached=True) - pair = self.get_valid_pair_combination(coin, currency) + try: + pair = self.get_valid_pair_combination(coin, currency) + except ValueError: + return None + ticker: Ticker | None = tickers.get(pair, None) if not ticker: tickers_other: Tickers = self.get_tickers( diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index af95e57485e..085982d1dfd 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2006,6 +2006,49 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): assert exchange.get_tickers() == {} +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_conversion_rate(default_conf_usdt, mocker, exchange_name): + api_mock = MagicMock() + tick = { + "ETH/USDT": { + "last": 42, + }, + "BCH/USDT": { + "last": 41, + }, + "ETH/BTC": { + "last": 250, + }, + } + tick2 = { + "XRP/USDT": { + "symbol": "XRP/USDT", + "bid": 0.5, + "ask": 1, + "last": 2.5, + } + } + mocker.patch(f"{EXMS}.exchange_has", return_value=True) + api_mock.fetch_tickers = MagicMock(side_effect=[tick, tick2]) + api_mock.fetch_bids_asks = MagicMock(return_value={}) + + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange=exchange_name) + # retrieve original ticker + assert exchange.get_conversion_rate("USDT", "USDT") == 1 + assert api_mock.fetch_tickers.call_count == 0 + assert exchange.get_conversion_rate("ETH", "USDT") == 42 + assert exchange.get_conversion_rate("ETH", "USDC") is None + assert exchange.get_conversion_rate("ETH", "BTC") == 250 + assert exchange.get_conversion_rate("BTC", "ETH") == 0.004 + + assert api_mock.fetch_tickers.call_count == 1 + api_mock.fetch_tickers.reset_mock() + + assert exchange.get_conversion_rate("XRP", "USDT") == 2.5 + # Only the call to the "others" market + assert api_mock.fetch_tickers.call_count == 1 + + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_ticker(default_conf, mocker, exchange_name): api_mock = MagicMock() From f07aec457a4f7f7fc3ca03ceac5c3baec53b3f03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 16:50:10 +0100 Subject: [PATCH 68/92] chore: remove unnecessary imports --- freqtrade/rpc/rpc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3c9a12c93b7..ac05f871410 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -32,7 +32,6 @@ ) from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs -from freqtrade.exchange.exchange_types import Ticker, Tickers from freqtrade.exchange.exchange_utils import price_to_precision from freqtrade.loggers import bufferHandler from freqtrade.persistence import KeyStoreKeys, KeyValueStore, PairLocks, Trade From 4d36aaff39e104b945fe478ed8efbf1e2f7aac1e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 16:54:12 +0100 Subject: [PATCH 69/92] feat: convert get_valid_pair_combination to generator --- freqtrade/exchange/exchange.py | 64 ++++++++++++++----------- tests/exchange/test_exchange.py | 19 ++++---- tests/freqtradebot/test_freqtradebot.py | 2 +- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_apiserver.py | 2 +- tests/rpc/test_rpc_telegram.py | 4 +- 6 files changed, 53 insertions(+), 40 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 875cff6ee28..55c7cff2b77 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,7 +7,7 @@ import inspect import logging import signal -from collections.abc import Coroutine +from collections.abc import Coroutine, Generator from copy import deepcopy from datetime import datetime, timedelta, timezone from math import floor, isnan @@ -705,14 +705,22 @@ def validate_stakecurrency(self, stake_currency: str) -> None: f"Available currencies are: {', '.join(quote_currencies)}" ) - def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str: + def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> Generator[str, None, None]: """ Get valid pair combination of curr_1 and curr_2 by trying both combinations. """ - for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]: + yielded = False + for pair in ( + f"{curr_1}/{curr_2}", + f"{curr_2}/{curr_1}", + f"{curr_1}/{curr_2}:{curr_2}", + f"{curr_2}/{curr_1}:{curr_1}", + ): if pair in self.markets and self.markets[pair].get("active"): - return pair - raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.") + yielded = True + yield pair + if not yielded: + raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.") def validate_timeframes(self, timeframe: str | None) -> None: """ @@ -1868,26 +1876,25 @@ def get_conversion_rate(self, coin: str, currency: str) -> float: return 1.0 tickers = self.get_tickers(cached=True) try: - pair = self.get_valid_pair_combination(coin, currency) + for pair in self.get_valid_pair_combination(coin, currency): + ticker: Ticker | None = tickers.get(pair, None) + if not ticker: + tickers_other: Tickers = self.get_tickers( + cached=True, + market_type=( + TradingMode.SPOT + if self.trading_mode != TradingMode.SPOT + else TradingMode.FUTURES + ), + ) + ticker = tickers_other.get(pair, None) + if ticker: + rate: float | None = ticker.get("last", None) + if rate and pair.startswith(currency) and not pair.endswith(currency): + rate = 1.0 / rate + return rate except ValueError: return None - - ticker: Ticker | None = tickers.get(pair, None) - if not ticker: - tickers_other: Tickers = self.get_tickers( - cached=True, - market_type=( - TradingMode.SPOT - if self.trading_mode != TradingMode.SPOT - else TradingMode.FUTURES - ), - ) - ticker = tickers_other.get(pair, None) - if ticker: - rate: float | None = ticker.get("last", None) - if rate and pair.startswith(currency) and not pair.endswith(currency): - rate = 1.0 / rate - return rate return None @retrier @@ -2244,10 +2251,13 @@ def calculate_fee_rate( # If cost is None or 0.0 -> falsy, return None return None try: - comb = self.get_valid_pair_combination(fee_curr, self._config["stake_currency"]) - tick = self.fetch_ticker(comb) - - fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask") + for comb in self.get_valid_pair_combination( + fee_curr, self._config["stake_currency"] + ): + tick = self.fetch_ticker(comb) + fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask") + if tick: + break except (ValueError, ExchangeError): fee_to_quote_rate = self._config["exchange"].get("unknown_fee_rate", None) if not fee_to_quote_rate: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 085982d1dfd..260dd8f0e55 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2021,10 +2021,7 @@ def test_get_conversion_rate(default_conf_usdt, mocker, exchange_name): }, } tick2 = { - "XRP/USDT": { - "symbol": "XRP/USDT", - "bid": 0.5, - "ask": 1, + "ADA/USDT:USDT": { "last": 2.5, } } @@ -2044,7 +2041,7 @@ def test_get_conversion_rate(default_conf_usdt, mocker, exchange_name): assert api_mock.fetch_tickers.call_count == 1 api_mock.fetch_tickers.reset_mock() - assert exchange.get_conversion_rate("XRP", "USDT") == 2.5 + assert exchange.get_conversion_rate("ADA", "USDT") == 2.5 # Only the call to the "others" market assert api_mock.fetch_tickers.call_count == 1 @@ -4122,10 +4119,16 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): ) ex = Exchange(default_conf) - assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC" - assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC" + assert next(ex.get_valid_pair_combination("ETH", "BTC")) == "ETH/BTC" + assert next(ex.get_valid_pair_combination("BTC", "ETH")) == "ETH/BTC" + multicombs = list(ex.get_valid_pair_combination("ETH", "USDT")) + assert len(multicombs) == 2 + assert "ETH/USDT" in multicombs + assert "ETH/USDT:USDT" in multicombs + with pytest.raises(ValueError, match=r"Could not combine.* to get a valid pair."): - ex.get_valid_pair_combination("NOPAIR", "ETH") + for x in ex.get_valid_pair_combination("NOPAIR", "ETH"): + pass @pytest.mark.parametrize( diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 21e5394a4a9..937bec8e5e9 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -4021,7 +4021,7 @@ def test_get_real_amount_fees_order( default_conf_usdt, market_buy_order_usdt_doublefee, fee, mocker ): tfo_mock = mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[]) - mocker.patch(f"{EXMS}.get_valid_pair_combination", return_value="BNB/USDT") + mocker.patch(f"{EXMS}.get_valid_pair_combination", return_value=["BNB/USDT"]) mocker.patch(f"{EXMS}.fetch_ticker", return_value={"last": 200}) trade = Trade( pair="LTC/USDT", diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 8fe59b5eace..225c5e36441 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -586,7 +586,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): fetch_positions=MagicMock(return_value=mock_pos), get_tickers=tickers, get_valid_pair_combination=MagicMock( - side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}" + side_effect=lambda a, b: [f"{b}/{a}" if a == "USDT" else f"{a}/{b}"] ), ) default_conf_usdt["dry_run"] = False diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index ee28bcc6cf9..221a8b666d5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -556,7 +556,7 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): ftbot.config["dry_run"] = False mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) mocker.patch(f"{EXMS}.get_tickers", tickers) - mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: [f"{a}/{b}"]) ftbot.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index bc7746aeffb..845bce37c74 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -960,7 +960,7 @@ async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance default_conf["dry_run"] = False mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) mocker.patch(f"{EXMS}.get_tickers", tickers) - mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: [f"{a}/{b}"]) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1049,7 +1049,7 @@ async def test_telegram_balance_handle_futures( mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) mocker.patch(f"{EXMS}.fetch_positions", return_value=mock_pos) mocker.patch(f"{EXMS}.get_tickers", tickers) - mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: [f"{a}/{b}"]) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) From f529cfe8cf2b395e445f24af5943aeaad8ab5611 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 16:58:31 +0100 Subject: [PATCH 70/92] chore: fix wrong return typing --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 55c7cff2b77..81142683903 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1863,7 +1863,7 @@ def get_tickers( except ccxt.BaseError as e: raise OperationalException(e) from e - def get_conversion_rate(self, coin: str, currency: str) -> float: + def get_conversion_rate(self, coin: str, currency: str) -> float | None: """ Quick and cached way to get conversion rate one currency to the other. Can then be used as "rate * amount" to convert between currencies. From d4fbdeee94fdb7af576da9cc9cd4e3d390ff3bff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:45:29 +0000 Subject: [PATCH 71/92] chore(deps-dev): bump the types group with 2 updates Bumps the types group with 2 updates: [types-tabulate](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed). Updates `types-tabulate` from 0.9.0.20240106 to 0.9.0.20241207 - [Commits](https://github.com/python/typeshed/commits) Updates `types-python-dateutil` from 2.9.0.20241003 to 2.9.0.20241206 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0640a2549bc..6591b4a5747 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,5 +28,5 @@ nbconvert==7.16.4 types-cachetools==5.5.0.20240820 types-filelock==3.2.7 types-requests==2.32.0.20241016 -types-tabulate==0.9.0.20240106 -types-python-dateutil==2.9.0.20241003 +types-tabulate==0.9.0.20241207 +types-python-dateutil==2.9.0.20241206 From f5dceb0b09e515bbab53f6d830d77ebf74007e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:46:15 +0000 Subject: [PATCH 72/92] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.5.47 to 9.5.48 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.47...9.5.48) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index e8e99f3376f..f342680e278 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.7 mkdocs==1.6.1 -mkdocs-material==9.5.47 +mkdocs-material==9.5.48 mdx_truly_sane_lists==1.3 pymdown-extensions==10.12 jinja2==3.1.4 From e97a647b5474c68200e75cabc4fbaaf9d02480ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:47:13 +0000 Subject: [PATCH 73/92] chore(deps-dev): bump ruff from 0.8.1 to 0.8.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.1 to 0.8.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.8.1...0.8.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0640a2549bc..baaa0e06d72 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.8.1 +ruff==0.8.2 mypy==1.13.0 pre-commit==4.0.1 pytest==8.3.4 From 83471228e7eeb70ec46f6a664c2c59488819a871 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:47:37 +0000 Subject: [PATCH 74/92] chore(deps): bump python-telegram-bot from 21.8 to 21.9 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.8 to 21.9. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.8...v21.9) --- updated-dependencies: - dependency-name: python-telegram-bot dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3719777e44..311a2f51001 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cryptography==42.0.8; platform_machine == 'armv7l' cryptography==44.0.0; platform_machine != 'armv7l' aiohttp==3.10.11 SQLAlchemy==2.0.36 -python-telegram-bot==21.8 +python-telegram-bot==21.9 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 humanize==4.11.0 From 8aea01545231b13659ca35d97daac943ad3c9481 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:47:48 +0000 Subject: [PATCH 75/92] chore(deps): bump ccxt from 4.4.35 to 4.4.37 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.35 to 4.4.37. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.4.35...4.4.37) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3719777e44..0597f25fa63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.2 pandas-ta==0.3.14b -ccxt==4.4.35 +ccxt==4.4.37 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==44.0.0; platform_machine != 'armv7l' aiohttp==3.10.11 From f1fabb07bf91205a7373578f29f6cba66debd23c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:47:54 +0000 Subject: [PATCH 76/92] chore(deps): bump fastapi from 0.115.5 to 0.115.6 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.5 to 0.115.6. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.115.5...0.115.6) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3719777e44..94e6c612bd1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ orjson==3.10.12 sdnotify==0.3.2 # API Server -fastapi==0.115.5 +fastapi==0.115.6 pydantic==2.10.2 uvicorn==0.32.1 pyjwt==2.10.1 From 678821467164dc87397cb0622bf92fc21f5bdecc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 05:32:50 +0000 Subject: [PATCH 77/92] chore(deps): bump pydantic from 2.10.2 to 2.10.3 Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.2 to 2.10.3. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.10.2...v2.10.3) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4a8d2c835cf..444539b6517 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ sdnotify==0.3.2 # API Server fastapi==0.115.6 -pydantic==2.10.2 +pydantic==2.10.3 uvicorn==0.32.1 pyjwt==2.10.1 aiofiles==24.1.0 From 8cb82df055b8013374d881a33cfbb14cb62ae402 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 17:20:24 +0100 Subject: [PATCH 78/92] chore: simplify dry_wallets call --- freqtrade/wallets.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index fb27d5e5d9a..75bd9a50578 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -145,9 +145,8 @@ def _update_dry(self) -> None: used=used_stake, total=total_stake, ) - for currency in self._start_cap: + for currency, bal in self._start_cap.items(): if currency not in _wallets: - bal = self._start_cap[currency] _wallets[currency] = Wallet(currency, bal, 0, bal) self._wallets = _wallets From 6887ed4bf7218d782cd0dc1f840413ab05a224c6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 19:38:25 +0100 Subject: [PATCH 79/92] feat: add cross margin balance logic --- freqtrade/wallets.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 75bd9a50578..4dcdb4ee44f 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -136,12 +136,22 @@ def _update_dry(self) -> None: used_stake = tot_in_trades + cross_margin = 0.0 + if self._config.get("margin_mode") == "cross": + # In cross-margin mode, the total balance is used as collateral. + for curr, bal in self._start_cap.items(): + if curr == self._stake_currency: + continue + rate = self._exchange.get_conversion_rate(curr, self._stake_currency) + if rate: + cross_margin += bal * rate + current_stake = self._start_cap.get(self._stake_currency, 0) + tot_profit - tot_in_trades total_stake = current_stake + used_stake _wallets[self._stake_currency] = Wallet( currency=self._stake_currency, - free=current_stake, + free=current_stake + cross_margin, used=used_stake, total=total_stake, ) From 0fc0b2a1be58e815f7927dd104464b6b121843ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Dec 2024 19:38:36 +0100 Subject: [PATCH 80/92] test: add test for cross balance logic --- tests/test_wallets.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index d5e49438a47..fc217ab8450 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -468,10 +468,33 @@ def test_check_exit_amount_futures(mocker, default_conf, fee): "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, }, ), + ( + { + "stake_currency": "USDT", + "margin_mode": "cross", + "dry_run_wallet": {"USDC": 1000.0, "BTC": 0.1, "ETH": 2.0}, + }, + { + # USDT wallet should be created with 0 balance, but Free balance, since + # it's converted from the other currencies + "USDT": {"currency": "USDT", "free": 4200.0, "used": 0.0, "total": 0.0}, + "USDC": {"currency": "USDC", "free": 1000.0, "used": 0.0, "total": 1000.0}, + "BTC": {"currency": "BTC", "free": 0.1, "used": 0.0, "total": 0.1}, + "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, + }, + ), ], ) def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallets): default_conf_usdt.update(config) + mocker.patch( + f"{EXMS}.get_tickers", + return_value={ + "USDC/USDT": {"last": 1.0}, + "BTC/USDT": {"last": 20_000.0}, + "ETH/USDT": {"last": 1100.0}, + }, + ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) # Verify each wallet matches the expected values @@ -507,7 +530,9 @@ def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallet assert freqtrade.wallets._wallets["NEO"].free == 45.04504504 # Verify USDT wallet was reduced by trade amount + stake_currency = config["stake_currency"] assert ( - pytest.approx(freqtrade.wallets._wallets["USDT"].total) == wallets["USDT"]["total"] - 100.0 + pytest.approx(freqtrade.wallets._wallets[stake_currency].total) + == wallets[stake_currency]["total"] - 100.0 ) assert len(freqtrade.wallets._wallets) == len(wallets) + 1 # Original wallets + NEO From 6b1ac499ba8702a041785f9bbf6024eddf61287f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Dec 2024 06:26:29 +0100 Subject: [PATCH 81/92] tests: more tests for cross wallet --- tests/test_wallets.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index fc217ab8450..c4d84f547d2 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -483,6 +483,35 @@ def test_check_exit_amount_futures(mocker, default_conf, fee): "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, }, ), + ( + { + "stake_currency": "USDT", + "margin_mode": "cross", + "dry_run_wallet": {"USDT": 500, "USDC": 1000.0, "BTC": 0.1, "ETH": 2.0}, + }, + { + # USDT wallet should be created with 500 balance, but Free balance, since + # it's converted from the other currencies + "USDT": {"currency": "USDT", "free": 4700.0, "used": 0.0, "total": 500.0}, + "USDC": {"currency": "USDC", "free": 1000.0, "used": 0.0, "total": 1000.0}, + "BTC": {"currency": "BTC", "free": 0.1, "used": 0.0, "total": 0.1}, + "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, + }, + ), + ( + # Same as above, but without cross + { + "stake_currency": "USDT", + "dry_run_wallet": {"USDT": 500, "USDC": 1000.0, "BTC": 0.1, "ETH": 2.0}, + }, + { + # No "free" transfer for USDT wallet + "USDT": {"currency": "USDT", "free": 500.0, "used": 0.0, "total": 500.0}, + "USDC": {"currency": "USDC", "free": 1000.0, "used": 0.0, "total": 1000.0}, + "BTC": {"currency": "BTC", "free": 0.1, "used": 0.0, "total": 0.1}, + "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, + }, + ), ], ) def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallets): From fe9d1a053e7b5ffb61307750cb71b339ea0bd90b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Dec 2024 06:40:49 +0100 Subject: [PATCH 82/92] chore: bump pre-commit dependencies --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f3d2ff27a1..f621489d0c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,8 +17,8 @@ repos: - types-cachetools==5.5.0.20240820 - types-filelock==3.2.7 - types-requests==2.32.0.20241016 - - types-tabulate==0.9.0.20240106 - - types-python-dateutil==2.9.0.20241003 + - types-tabulate==0.9.0.20241207 + - types-python-dateutil==2.9.0.20241206 - SQLAlchemy==2.0.36 # stages: [push] From e9cf0a71d822d4048c300da4c37713d2be3799fd Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 10 Dec 2024 05:05:39 +0000 Subject: [PATCH 83/92] chore: update pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f621489d0c0..745cc391fe4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.8.1' + rev: 'v0.8.2' hooks: - id: ruff - id: ruff-format From 097836d1930822a59abe990ea91e8f3dcde65b46 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Dec 2024 20:49:43 +0100 Subject: [PATCH 84/92] feat: improve logic for liquidation price calc --- freqtrade/leverage/liquidation_price.py | 7 +++++-- freqtrade/wallets.py | 14 ++++++++++++++ tests/leverage/test_update_liquidation_price.py | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py index aed8c9160a9..4b49e455154 100644 --- a/freqtrade/leverage/liquidation_price.py +++ b/freqtrade/leverage/liquidation_price.py @@ -27,9 +27,12 @@ def update_liquidation_prices( total_wallet_stake = 0.0 if dry_run: # Parameters only needed for cross margin - total_wallet_stake = wallets.get_total(stake_currency) + total_wallet_stake = wallets.get_collateral() - logger.info("Updating liquidation price for all open trades.") + logger.info( + "Updating liquidation price for all open trades. " + f"Collateral {total_wallet_stake} {stake_currency}." + ) open_trades = Trade.get_open_trades() for t in open_trades: # TODO: This should be done in a batch update diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 4dcdb4ee44f..5a239dadda4 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -73,6 +73,18 @@ def get_total(self, currency: str) -> float: else: return 0 + def get_collateral(self) -> float: + """ + Get total collateral for liquidation price calculation. + """ + if self._config.get("margin_mode") == "cross": + # free includes all balances and, combined with position collateral, + # is used as "wallet balance". + return self.get_free(self._stake_currency) + sum( + pos.collateral for pos in self._positions.values() + ) + return self.get_total(self._stake_currency) + def get_owned(self, pair: str, base_currency: str) -> float: """ Get currently owned value. @@ -139,6 +151,8 @@ def _update_dry(self) -> None: cross_margin = 0.0 if self._config.get("margin_mode") == "cross": # In cross-margin mode, the total balance is used as collateral. + # This is moved as "free" into the stake currency balance. + # strongly tied to the get_collateral() implementation. for curr, bal in self._start_cap.items(): if curr == self._stake_currency: continue diff --git a/tests/leverage/test_update_liquidation_price.py b/tests/leverage/test_update_liquidation_price.py index 1b25babd039..68c35509fc3 100644 --- a/tests/leverage/test_update_liquidation_price.py +++ b/tests/leverage/test_update_liquidation_price.py @@ -29,7 +29,7 @@ def test_update_liquidation_prices(mocker, margin_mode, dry_run): assert trade_mock.set_liquidation_price.call_count == 1 - assert wallets.get_total.call_count == ( + assert wallets.get_collateral.call_count == ( 0 if margin_mode == MarginMode.ISOLATED or not dry_run else 1 ) From f3d7b249be98bb9776fe370536346d3e858dbed0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Dec 2024 21:04:49 +0100 Subject: [PATCH 85/92] test: Improve wallet tests --- tests/test_wallets.py | 62 +++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index c4d84f547d2..36211dcab40 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -512,6 +512,23 @@ def test_check_exit_amount_futures(mocker, default_conf, fee): "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, }, ), + ( + # Same as above, but with futures and cross + { + "stake_currency": "USDT", + "margin_mode": "cross", + "trading_mode": "futures", + "dry_run_wallet": {"USDT": 500, "USDC": 1000.0, "BTC": 0.1, "ETH": 2.0}, + }, + { + # USDT wallet should be created with 500 balance, but Free balance, since + # it's converted from the other currencies + "USDT": {"currency": "USDT", "free": 4700.0, "used": 0.0, "total": 500.0}, + "USDC": {"currency": "USDC", "free": 1000.0, "used": 0.0, "total": 1000.0}, + "BTC": {"currency": "BTC", "free": 0.1, "used": 0.0, "total": 0.1}, + "ETH": {"currency": "ETH", "free": 2.0, "used": 0.0, "total": 2.0}, + }, + ), ], ) def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallets): @@ -525,7 +542,7 @@ def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallet }, ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - + stake_currency = config["stake_currency"] # Verify each wallet matches the expected values for currency, expected_wallet in wallets.items(): wallet = freqtrade.wallets._wallets[currency] @@ -548,20 +565,37 @@ def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallet "last": 0.22, }, ) + # Without position, collateral will be the same as free + assert freqtrade.wallets.get_collateral() == freqtrade.wallets.get_free(stake_currency) freqtrade.execute_entry("NEO/USDT", 100.0) # Update wallets and verify NEO is now included freqtrade.wallets.update() - assert "NEO" in freqtrade.wallets._wallets - - assert freqtrade.wallets._wallets["NEO"].total == 45.04504504 # 100 USDT / 0.22 - assert freqtrade.wallets._wallets["NEO"].used == 0.0 - assert freqtrade.wallets._wallets["NEO"].free == 45.04504504 - - # Verify USDT wallet was reduced by trade amount - stake_currency = config["stake_currency"] - assert ( - pytest.approx(freqtrade.wallets._wallets[stake_currency].total) - == wallets[stake_currency]["total"] - 100.0 - ) - assert len(freqtrade.wallets._wallets) == len(wallets) + 1 # Original wallets + NEO + if default_conf_usdt["trading_mode"] != "futures": + assert "NEO" in freqtrade.wallets._wallets + + assert freqtrade.wallets._wallets["NEO"].total == 45.04504504 # 100 USDT / 0.22 + assert freqtrade.wallets._wallets["NEO"].used == 0.0 + assert freqtrade.wallets._wallets["NEO"].free == 45.04504504 + assert freqtrade.wallets.get_collateral() == freqtrade.wallets.get_free(stake_currency) + # Verify USDT wallet was reduced by trade amount + assert ( + pytest.approx(freqtrade.wallets._wallets[stake_currency].total) + == wallets[stake_currency]["total"] - 100.0 + ) + assert len(freqtrade.wallets._wallets) == len(wallets) + 1 # Original wallets + NEO + else: + # Futures mode + assert "NEO" not in freqtrade.wallets._wallets + assert freqtrade.wallets._positions["NEO/USDT"].position == 45.04504504 + assert pytest.approx(freqtrade.wallets._positions["NEO/USDT"].collateral) == 100 + + # Verify USDT wallet's free was reduced by trade amount + assert ( + pytest.approx(freqtrade.wallets.get_collateral()) + == freqtrade.wallets.get_free(stake_currency) + 100 + ) + assert ( + pytest.approx(freqtrade.wallets._wallets[stake_currency].free) + == wallets[stake_currency]["free"] - 100.0 + ) From d8fa782f13662e7f223405e8793b35a8c1adefa4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Dec 2024 07:07:47 +0100 Subject: [PATCH 86/92] test: Add test with faulty behavior part of #11074 --- freqtrade/plugins/pairlist/PercentChangePairList.py | 1 - tests/conftest.py | 4 ++-- tests/plugins/test_percentchangepairlist.py | 12 +++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index cbc86f259d4..98485359102 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -191,7 +191,6 @@ def gen_pairlist(self, tickers: Tickers) -> list[str]: for k, v in tickers.items() if ( self._exchange.get_pair_quote_currency(k) == self._stake_currency - and (self._use_range or v.get("percentage") is not None) and v["symbol"] in _pairlist ) ] diff --git a/tests/conftest.py b/tests/conftest.py index df8a31974c7..69ea8878b09 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2237,7 +2237,7 @@ def tickers(): "first": None, "last": 8603.67, "change": -0.879, - "percentage": None, + "percentage": -8.95, "average": None, "baseVolume": 30414.604298, "quoteVolume": 259629896.48584127, @@ -2281,7 +2281,7 @@ def tickers(): "first": None, "last": 129.28, "change": 1.795, - "percentage": None, + "percentage": -2.5, "average": None, "baseVolume": 59698.79897, "quoteVolume": 29132399.743954, diff --git a/tests/plugins/test_percentchangepairlist.py b/tests/plugins/test_percentchangepairlist.py index df165cf9860..9d661c9c479 100644 --- a/tests/plugins/test_percentchangepairlist.py +++ b/tests/plugins/test_percentchangepairlist.py @@ -360,9 +360,15 @@ def test_gen_pairlist_from_tickers(mocker, rpl_config, tickers): exchange = get_patched_exchange(mocker, rpl_config, exchange="binance") pairlistmanager = PairListManager(exchange, rpl_config) - remote_pairlist = PercentChangePairList( - exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0 - ) + remote_pairlist = pairlistmanager._pairlist_handlers[0] + + # The generator returns BTC ETH and TKN - filtering the first ensures removing pairs in this step ain't problematic. + def _validate_pair(pair, ticker): + if pair == "BTC/USDT": + return False + return True + + remote_pairlist._validate_pair = _validate_pair result = remote_pairlist.gen_pairlist(tickers.return_value) From b9aa78b987a299bc97dd2c440bcb6827a9b4d522 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Dec 2024 07:12:50 +0100 Subject: [PATCH 87/92] fix: crash in PercentChange pairlist Could happen if pairs were removed due to empty Ticker when the pairlist is not a generator. closes #11074 --- .../plugins/pairlist/PercentChangePairList.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index 98485359102..149e3977f5d 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -217,7 +217,7 @@ def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: self.fetch_percent_change_from_lookback_period(filtered_tickers) else: # Fetching 24h change by default from supported exchange tickers - self.fetch_percent_change_from_tickers(filtered_tickers, tickers) + filtered_tickers = self.fetch_percent_change_from_tickers(filtered_tickers, tickers) if self._min_value is not None: filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value] @@ -261,7 +261,6 @@ def fetch_candles_for_lookback_period( ) * 1000 ) - # todo: utc date output for starting date self.log_once( f"Using change range of {self._lookback_period} candles, timeframe: " f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " @@ -302,15 +301,21 @@ def fetch_percent_change_from_lookback_period(self, filtered_tickers: list[dict[ else: filtered_tickers[i]["percentage"] = 0 - def fetch_percent_change_from_tickers(self, filtered_tickers: list[dict[str, Any]], tickers): - for i, p in enumerate(filtered_tickers): + def fetch_percent_change_from_tickers( + self, filtered_tickers: list[dict[str, Any]], tickers + ) -> list[dict[str, Any]]: + valid_tickers: list[dict[str, Any]] = [] + for p in filtered_tickers: # Filter out assets - if not self._validate_pair( - p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None + if ( + self._validate_pair( + p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None + ) + and p["symbol"] != "UNI/USDT" ): - filtered_tickers.remove(p) - else: - filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"] + p["percentage"] = tickers[p["symbol"]]["percentage"] + valid_tickers.append(p) + return valid_tickers def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool: """ From 34835752f815774f99848e9b36b34027fd9c9b14 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Dec 2024 07:25:07 +0100 Subject: [PATCH 88/92] chore: improve typing in percentChange Pairlist --- .../plugins/pairlist/PercentChangePairList.py | 28 +++++++++++++------ tests/plugins/test_percentchangepairlist.py | 3 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index 149e3977f5d..4f91a47d11d 100644 --- a/freqtrade/plugins/pairlist/PercentChangePairList.py +++ b/freqtrade/plugins/pairlist/PercentChangePairList.py @@ -8,7 +8,7 @@ import logging from datetime import timedelta -from typing import Any +from typing import TypedDict from cachetools import TTLCache from pandas import DataFrame @@ -24,6 +24,11 @@ logger = logging.getLogger(__name__) +class SymbolWithPercentage(TypedDict): + symbol: str + percentage: float | None + + class PercentChangePairList(IPairList): is_pairlist_generator = True supports_backtesting = SupportsBacktesting.NO @@ -211,10 +216,12 @@ def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: new whitelist """ - filtered_tickers: list[dict[str, Any]] = [{"symbol": k} for k in pairlist] + filtered_tickers: list[SymbolWithPercentage] = [ + {"symbol": k, "percentage": None} for k in pairlist + ] if self._use_range: # calculating using lookback_period - self.fetch_percent_change_from_lookback_period(filtered_tickers) + filtered_tickers = self.fetch_percent_change_from_lookback_period(filtered_tickers) else: # Fetching 24h change by default from supported exchange tickers filtered_tickers = self.fetch_percent_change_from_tickers(filtered_tickers, tickers) @@ -227,7 +234,7 @@ def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: sorted_tickers = sorted( filtered_tickers, reverse=self._sort_direction == "desc", - key=lambda t: t["percentage"], + key=lambda t: t["percentage"], # type: ignore ) # Validate whitelist to only have active market pairs @@ -239,7 +246,7 @@ def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: return pairs def fetch_candles_for_lookback_period( - self, filtered_tickers: list[dict[str, str]] + self, filtered_tickers: list[SymbolWithPercentage] ) -> dict[PairWithTimeframe, DataFrame]: since_ms = ( int( @@ -275,7 +282,9 @@ def fetch_candles_for_lookback_period( candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) return candles - def fetch_percent_change_from_lookback_period(self, filtered_tickers: list[dict[str, Any]]): + def fetch_percent_change_from_lookback_period( + self, filtered_tickers: list[SymbolWithPercentage] + ) -> list[SymbolWithPercentage]: # get lookback period in ms, for exchange ohlcv fetch candles = self.fetch_candles_for_lookback_period(filtered_tickers) @@ -300,11 +309,12 @@ def fetch_percent_change_from_lookback_period(self, filtered_tickers: list[dict[ filtered_tickers[i]["percentage"] = pct_change else: filtered_tickers[i]["percentage"] = 0 + return filtered_tickers def fetch_percent_change_from_tickers( - self, filtered_tickers: list[dict[str, Any]], tickers - ) -> list[dict[str, Any]]: - valid_tickers: list[dict[str, Any]] = [] + self, filtered_tickers: list[SymbolWithPercentage], tickers + ) -> list[SymbolWithPercentage]: + valid_tickers: list[SymbolWithPercentage] = [] for p in filtered_tickers: # Filter out assets if ( diff --git a/tests/plugins/test_percentchangepairlist.py b/tests/plugins/test_percentchangepairlist.py index 9d661c9c479..6f399fc9f43 100644 --- a/tests/plugins/test_percentchangepairlist.py +++ b/tests/plugins/test_percentchangepairlist.py @@ -362,7 +362,8 @@ def test_gen_pairlist_from_tickers(mocker, rpl_config, tickers): remote_pairlist = pairlistmanager._pairlist_handlers[0] - # The generator returns BTC ETH and TKN - filtering the first ensures removing pairs in this step ain't problematic. + # The generator returns BTC ETH and TKN - filtering the first ensures removing pairs + # in this step ain't problematic. def _validate_pair(pair, ticker): if pair == "BTC/USDT": return False From 467c0dfffa3b3ec2b4bfd45b49666e060266bb5b Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:16:05 +0000 Subject: [PATCH 89/92] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 3480 ++++++++++++----- 1 file changed, 2411 insertions(+), 1069 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 69bfb8016c6..4bd8a749041 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -315,13 +315,13 @@ "symbol": "1000BONK/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 60000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "60000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -331,119 +331,119 @@ "tier": 3.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "350.0" + "cum": "550.0" } }, { "tier": 4.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "1850.0" + "cum": "3050.0" } }, { "tier": 5.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "600000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "16850.0" + "cum": "28050.0" } }, { "tier": 6.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "166850.0" + "cum": "278050.0" } }, { "tier": 7.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "316850.0" + "cum": "528050.0" } }, { "tier": 8.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1254350.0" + "cum": "2090550.0" } }, { "tier": 9.0, "symbol": "1000BONK/USDT:USDT", "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5004350.0" + "cum": "8340550.0" } } ], @@ -3209,13 +3209,13 @@ "symbol": "ACT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "80000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -3225,119 +3225,274 @@ "tier": 3.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 80000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "400000", + "notionalFloor": "80000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "450.0" } }, { "tier": 4.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "2450.0" } }, { "tier": 5.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 800000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "4000000", + "notionalFloor": "800000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "22450.0" } }, { "tier": 6.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "222450.0" } }, { "tier": 7.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "422450.0" } }, { "tier": 8.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "1672450.0" } }, { "tier": 9.0, "symbol": "ACT/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "6672450.0" + } + } + ], + "ACX/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", + "cum": "150.0" + } + }, + { + "tier": 4.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" + } + }, + { + "tier": 5.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" + } + }, + { + "tier": 6.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "55650.0" + } + }, + { + "tier": 7.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "2500000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "105650.0" + } + }, + { + "tier": 8.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 2500000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2500000", + "maintMarginRatio": "0.25", + "cum": "418150.0" + } + }, + { + "tier": 9.0, + "symbol": "ACX/USDT:USDT", + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1668150.0" } } ], @@ -4502,15 +4657,15 @@ "symbol": "ALGO/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -4518,102 +4673,136 @@ "tier": 2.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "40000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "symbol": "ALGO/USDT:USDT", + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.02", + "cum": "250.0" + } + }, + { + "tier": 4.0, + "symbol": "ALGO/USDT:USDT", + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "4", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "notionalCap": "400000", + "notionalFloor": "200000", "maintMarginRatio": "0.025", - "cum": "50.0" + "cum": "1250.0" } }, { - "tier": 3.0, + "tier": 5.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "notionalCap": "2000000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "675.0" + "cum": "11250.0" } }, { - "tier": 4.0, + "tier": 6.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "4000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "5675.0" + "cum": "111250.0" } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "5000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.125", - "cum": "11925.0" + "cum": "211250.0" } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "136925.0" + "cum": "836250.0" } }, { - "tier": 7.0, + "tier": 9.0, "symbol": "ALGO/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "636925.0" + "cum": "3336250.0" } } ], @@ -6243,13 +6432,13 @@ "symbol": "ARB/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 80000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 40.0, "info": { "bracket": "3", "initialLeverage": "40", - "notionalCap": "80000", + "notionalCap": "100000", "notionalFloor": "50000", "maintMarginRatio": "0.015", "cum": "270.0" @@ -6259,119 +6448,119 @@ "tier": 4.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 300000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "300000", - "notionalFloor": "80000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "670.0" + "cum": "770.0" } }, { "tier": 5.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "5", "initialLeverage": "20", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "2170.0" + "cum": "3270.0" } }, { "tier": 6.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "600000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "17170.0" + "cum": "28270.0" } }, { "tier": 7.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "167170.0" + "cum": "278270.0" } }, { "tier": 8.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "317170.0" + "cum": "528270.0" } }, { "tier": 9.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1254670.0" + "cum": "2090770.0" } }, { "tier": 10.0, "symbol": "ARB/USDT:USDT", "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5004670.0" + "cum": "8340770.0" } } ], @@ -7536,13 +7725,13 @@ "symbol": "AVAX/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "25000", "notionalFloor": "0", "maintMarginRatio": "0.005", "cum": "0.0" @@ -7552,153 +7741,153 @@ "tier": 2.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.0065, + "minNotional": 25000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "notionalCap": "80000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "125.0" } }, { "tier": 3.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.01, + "minNotional": 80000.0, + "maxNotional": 160000.0, + "maintenanceMarginRate": 0.015, "maxLeverage": 40.0, "info": { "bracket": "3", "initialLeverage": "40", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.01", - "cum": "95.0" + "notionalCap": "160000", + "notionalFloor": "80000", + "maintMarginRatio": "0.015", + "cum": "525.0" } }, { "tier": 4.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 400000.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "400000", - "notionalFloor": "80000", + "notionalCap": "800000", + "notionalFloor": "160000", "maintMarginRatio": "0.02", - "cum": "895.0" + "cum": "1325.0" } }, { "tier": 5.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 800000.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "5", "initialLeverage": "20", - "notionalCap": "800000", - "notionalFloor": "400000", + "notionalCap": "1600000", + "notionalFloor": "800000", "maintMarginRatio": "0.025", - "cum": "2895.0" + "cum": "5325.0" } }, { "tier": 6.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 4000000.0, + "minNotional": 1600000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "4000000", - "notionalFloor": "800000", + "notionalCap": "8000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.05", - "cum": "22895.0" + "cum": "45325.0" } }, { "tier": 7.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 8000000.0, + "maxNotional": 16000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "16000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.1", - "cum": "222895.0" + "cum": "445325.0" } }, { "tier": 8.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 8000000.0, - "maxNotional": 10000000.0, + "minNotional": 16000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "8000000", + "notionalCap": "20000000", + "notionalFloor": "16000000", "maintMarginRatio": "0.125", - "cum": "422895.0" + "cum": "845325.0" } }, { "tier": 9.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1672895.0" + "cum": "3345325.0" } }, { "tier": 10.0, "symbol": "AVAX/USDT:USDT", "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 50000000.0, + "minNotional": 40000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalCap": "80000000", + "notionalFloor": "40000000", "maintMarginRatio": "0.5", - "cum": "6672895.0" + "cum": "13345325.0" } } ], @@ -8381,13 +8570,13 @@ "symbol": "BAN/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -8397,136 +8586,136 @@ "tier": 2.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", + "notionalCap": "30000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 30000.0, + "minNotional": 30000.0, + "maxNotional": 150000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "30000", - "notionalFloor": "10000", + "notionalCap": "150000", + "notionalFloor": "30000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "200.0" } }, { "tier": 4.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 60000.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "60000", - "notionalFloor": "30000", + "notionalCap": "300000", + "notionalFloor": "150000", "maintMarginRatio": "0.025", - "cum": "225.0" + "cum": "950.0" } }, { "tier": 5.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "1500000", + "notionalFloor": "300000", "maintMarginRatio": "0.05", - "cum": "1725.0" + "cum": "8450.0" } }, { "tier": 6.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.1", - "cum": "16725.0" + "cum": "83450.0" } }, { "tier": 7.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 750000.0, + "minNotional": 3000000.0, + "maxNotional": 3750000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "600000", + "notionalCap": "3750000", + "notionalFloor": "3000000", "maintMarginRatio": "0.125", - "cum": "31725.0" + "cum": "158450.0" } }, { "tier": 8.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, + "minNotional": 3750000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "750000", + "notionalCap": "7500000", + "notionalFloor": "3750000", "maintMarginRatio": "0.25", - "cum": "125475.0" + "cum": "627200.0" } }, { "tier": 9.0, "symbol": "BAN/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.5", - "cum": "500475.0" + "cum": "2502200.0" } } ], @@ -10175,10 +10364,10 @@ "minNotional": 0.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "0", "maintMarginRatio": "0.025", @@ -10192,10 +10381,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -10241,13 +10430,13 @@ "symbol": "BLZ/USDT:USDT", "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 1300000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1500000", + "notionalCap": "1300000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", "cum": "136875.0" @@ -10257,17 +10446,17 @@ "tier": 6.0, "symbol": "BLZ/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 1300000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "1500000", + "notionalFloor": "1300000", "maintMarginRatio": "0.5", - "cum": "511875.0" + "cum": "461875.0" } } ], @@ -11192,10 +11381,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 21.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -11209,10 +11398,10 @@ "minNotional": 5000.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "10000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -11226,10 +11415,10 @@ "minNotional": 10000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.05", @@ -11311,13 +11500,13 @@ "symbol": "BRETT/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -11327,136 +11516,136 @@ "tier": 2.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", + "notionalCap": "20000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 30000.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "30000", - "notionalFloor": "10000", + "notionalCap": "100000", + "notionalFloor": "20000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "150.0" } }, { "tier": 4.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 60000.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "60000", - "notionalFloor": "30000", + "notionalCap": "200000", + "notionalFloor": "100000", "maintMarginRatio": "0.025", - "cum": "225.0" + "cum": "650.0" } }, { "tier": 5.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.05", - "cum": "1725.0" + "cum": "5650.0" } }, { "tier": 6.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "16725.0" + "cum": "55650.0" } }, { "tier": 7.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 750000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "600000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "31725.0" + "cum": "105650.0" } }, { "tier": 8.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "750000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "125475.0" + "cum": "418150.0" } }, { "tier": 9.0, "symbol": "BRETT/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "500475.0" + "cum": "1668150.0" } } ], @@ -13776,13 +13965,13 @@ "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -13792,136 +13981,136 @@ "tier": 2.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", + "notionalCap": "20000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 30000.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "30000", - "notionalFloor": "10000", + "notionalCap": "100000", + "notionalFloor": "20000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "150.0" } }, { "tier": 4.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 60000.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "60000", - "notionalFloor": "30000", + "notionalCap": "200000", + "notionalFloor": "100000", "maintMarginRatio": "0.025", - "cum": "225.0" + "cum": "650.0" } }, { "tier": 5.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.05", - "cum": "1725.0" + "cum": "5650.0" } }, { "tier": 6.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "16725.0" + "cum": "55650.0" } }, { "tier": 7.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 750000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "600000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "31725.0" + "cum": "105650.0" } }, { "tier": 8.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "750000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "125475.0" + "cum": "418150.0" } }, { "tier": 9.0, "symbol": "CHILLGUY/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "500475.0" + "cum": "1668150.0" } } ], @@ -15275,118 +15464,135 @@ "symbol": "CRV/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 200000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, + "info": { + "bracket": "3", + "initialLeverage": "40", + "notionalCap": "80000", + "notionalFloor": "50000", + "maintMarginRatio": "0.015", + "cum": "285.0" + } + }, + { + "tier": 4.0, + "symbol": "CRV/USDT:USDT", + "currency": "USDT", + "minNotional": 80000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "50000", + "notionalCap": "300000", + "notionalFloor": "80000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "685.0" } }, { - "tier": 4.0, + "tier": 5.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "1535.0" + "cum": "2185.0" } }, { - "tier": 5.0, + "tier": 6.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "11535.0" + "cum": "17185.0" } }, { - "tier": 6.0, + "tier": 7.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "111535.0" + "cum": "167185.0" } }, { - "tier": 7.0, + "tier": 8.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "211535.0" + "cum": "317185.0" } }, { - "tier": 8.0, + "tier": 9.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "836535.0" + "cum": "1254685.0" } }, { - "tier": 9.0, + "tier": 10.0, "symbol": "CRV/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "3336535.0" + "cum": "5004685.0" } } ], @@ -16106,14 +16312,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -16122,15 +16328,15 @@ "symbol": "DASH/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, @@ -16138,102 +16344,119 @@ "tier": 3.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "DASH/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "125.0" + "cum": "325.0" } }, { - "tier": 4.0, + "tier": 5.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "750.0" + "cum": "2825.0" } }, { - "tier": 5.0, + "tier": 6.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "10750.0" + "cum": "27825.0" } }, { - "tier": 6.0, + "tier": 7.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", + "notionalCap": "1250000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "20750.0" + "cum": "52825.0" } }, { - "tier": 7.0, + "tier": 8.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1250000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2500000", + "notionalFloor": "1250000", "maintMarginRatio": "0.25", - "cum": "83250.0" + "cum": "209075.0" } }, { - "tier": 8.0, + "tier": 9.0, "symbol": "DASH/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "834075.0" } } ], @@ -20636,13 +20859,13 @@ "symbol": "ETHFI/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 40000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "20000", + "notionalCap": "40000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -20652,119 +20875,119 @@ "tier": 3.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 100000.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "20000", + "notionalCap": "200000", + "notionalFloor": "40000", "maintMarginRatio": "0.02", - "cum": "150.0" + "cum": "250.0" } }, { "tier": 4.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "400000", + "notionalFloor": "200000", "maintMarginRatio": "0.025", - "cum": "650.0" + "cum": "1250.0" } }, { "tier": 5.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "200000", + "notionalCap": "2000000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "5650.0" + "cum": "11250.0" } }, { "tier": 6.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "4000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "55650.0" + "cum": "111250.0" } }, { "tier": 7.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", + "notionalCap": "5000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.125", - "cum": "105650.0" + "cum": "211250.0" } }, { "tier": 8.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "418150.0" + "cum": "836250.0" } }, { "tier": 9.0, "symbol": "ETHFI/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "1668150.0" + "cum": "3336250.0" } } ], @@ -20946,13 +21169,13 @@ "symbol": "FET/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 60000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "60000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -20962,119 +21185,119 @@ "tier": 3.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "350.0" + "cum": "550.0" } }, { "tier": 4.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "1850.0" + "cum": "3050.0" } }, { "tier": 5.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "600000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "16850.0" + "cum": "28050.0" } }, { "tier": 6.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "166850.0" + "cum": "278050.0" } }, { "tier": 7.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "316850.0" + "cum": "528050.0" } }, { "tier": 8.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1254350.0" + "cum": "2090550.0" } }, { "tier": 9.0, "symbol": "FET/USDT:USDT", "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5004350.0" + "cum": "8340550.0" } } ], @@ -21462,13 +21685,13 @@ "symbol": "FIL/USDT:USDT", "currency": "USDT", "minNotional": 600000.0, - "maxNotional": 800000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "5", "initialLeverage": "20", - "notionalCap": "800000", + "notionalCap": "1000000", "notionalFloor": "600000", "maintMarginRatio": "0.025", "cum": "6200.0" @@ -21478,85 +21701,85 @@ "tier": 6.0, "symbol": "FIL/USDT:USDT", "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "800000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "26200.0" + "cum": "31200.0" } }, { "tier": 7.0, "symbol": "FIL/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "176200.0" + "cum": "281200.0" } }, { "tier": 8.0, "symbol": "FIL/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "326200.0" + "cum": "531200.0" } }, { "tier": 9.0, "symbol": "FIL/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1576200.0" + "cum": "2093700.0" } }, { "tier": 10.0, "symbol": "FIL/USDT:USDT", "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "20000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "6576200.0" + "cum": "8343700.0" } } ], @@ -22171,10 +22394,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 75.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "75", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -22188,10 +22411,10 @@ "minNotional": 5000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "50", + "initialLeverage": "15", "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", @@ -22205,10 +22428,10 @@ "minNotional": 50000.0, "maxNotional": 80000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 40.0, + "maxLeverage": 12.0, "info": { "bracket": "3", - "initialLeverage": "40", + "initialLeverage": "12", "notionalCap": "80000", "notionalFloor": "50000", "maintMarginRatio": "0.015", @@ -22222,10 +22445,10 @@ "minNotional": 80000.0, "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "25", + "initialLeverage": "10", "notionalCap": "300000", "notionalFloor": "80000", "maintMarginRatio": "0.02", @@ -22239,10 +22462,10 @@ "minNotional": 300000.0, "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "5", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "600000", "notionalFloor": "300000", "maintMarginRatio": "0.025", @@ -22256,10 +22479,10 @@ "minNotional": 600000.0, "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "6", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "3000000", "notionalFloor": "600000", "maintMarginRatio": "0.05", @@ -22288,13 +22511,13 @@ "symbol": "FTM/USDT:USDT", "currency": "USDT", "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "maxNotional": 7000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "7500000", + "notionalCap": "7000000", "notionalFloor": "6000000", "maintMarginRatio": "0.125", "cum": "317170.0" @@ -22304,34 +22527,34 @@ "tier": 9.0, "symbol": "FTM/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 7000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "8000000", + "notionalFloor": "7000000", "maintMarginRatio": "0.25", - "cum": "1254670.0" + "cum": "1192170.0" } }, { "tier": 10.0, "symbol": "FTM/USDT:USDT", "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "10000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.5", - "cum": "5004670.0" + "cum": "3192170.0" } } ], @@ -23791,13 +24014,13 @@ "symbol": "GRASS/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -23807,136 +24030,136 @@ "tier": 2.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 16000.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "16000", - "notionalFloor": "5000", + "notionalCap": "30000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 16000.0, - "maxNotional": 80000.0, + "minNotional": 30000.0, + "maxNotional": 150000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "80000", - "notionalFloor": "16000", + "notionalCap": "150000", + "notionalFloor": "30000", "maintMarginRatio": "0.02", - "cum": "105.0" + "cum": "200.0" } }, { "tier": 4.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 160000.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "160000", - "notionalFloor": "80000", + "notionalCap": "300000", + "notionalFloor": "150000", "maintMarginRatio": "0.025", - "cum": "505.0" + "cum": "950.0" } }, { "tier": 5.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 160000.0, - "maxNotional": 800000.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "160000", + "notionalCap": "1500000", + "notionalFloor": "300000", "maintMarginRatio": "0.05", - "cum": "4505.0" + "cum": "8450.0" } }, { "tier": 6.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.1", - "cum": "44505.0" + "cum": "83450.0" } }, { "tier": 7.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 3000000.0, + "maxNotional": 3750000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "3750000", + "notionalFloor": "3000000", "maintMarginRatio": "0.125", - "cum": "84505.0" + "cum": "158450.0" } }, { "tier": 8.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 3750000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "7500000", + "notionalFloor": "3750000", "maintMarginRatio": "0.25", - "cum": "334505.0" + "cum": "627200.0" } }, { "tier": 9.0, "symbol": "GRASS/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.5", - "cum": "1334505.0" + "cum": "2502200.0" } } ], @@ -24239,13 +24462,13 @@ "symbol": "HBAR/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 40000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "20000", + "notionalCap": "40000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -24255,119 +24478,119 @@ "tier": 3.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 100000.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "20000", + "notionalCap": "200000", + "notionalFloor": "40000", "maintMarginRatio": "0.02", - "cum": "150.0" + "cum": "250.0" } }, { "tier": 4.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "400000", + "notionalFloor": "200000", "maintMarginRatio": "0.025", - "cum": "650.0" + "cum": "1250.0" } }, { "tier": 5.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "200000", + "notionalCap": "2000000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "5650.0" + "cum": "11250.0" } }, { "tier": 6.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "4000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "55650.0" + "cum": "111250.0" } }, { "tier": 7.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", + "notionalCap": "5000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.125", - "cum": "105650.0" + "cum": "211250.0" } }, { "tier": 8.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "418150.0" + "cum": "836250.0" } }, { "tier": 9.0, "symbol": "HBAR/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "1668150.0" + "cum": "3336250.0" } } ], @@ -26137,13 +26360,13 @@ "symbol": "INJ/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "80000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -26153,119 +26376,119 @@ "tier": 3.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 80000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "400000", + "notionalFloor": "80000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "450.0" } }, { "tier": 4.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "2450.0" } }, { "tier": 5.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 800000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "4000000", + "notionalFloor": "800000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "22450.0" } }, { "tier": 6.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "222450.0" } }, { "tier": 7.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "422450.0" } }, { "tier": 8.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 12000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "12000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "1672450.0" } }, { "tier": 9.0, "symbol": "INJ/USDT:USDT", "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "12000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "3836250.0" + "cum": "6672450.0" } } ], @@ -28407,6 +28630,161 @@ } } ], + "KOMA/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "KOMA/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "KSM/USDT:USDT": [ { "tier": 1.0, @@ -31536,10 +31914,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -31553,10 +31931,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -31570,10 +31948,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -31925,6 +32303,161 @@ } } ], + "ME/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "ME/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "MEME/USDT:USDT": [ { "tier": 1.0, @@ -32959,6 +33492,161 @@ } } ], + "MOVE/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "MOVE/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "MOVR/USDT:USDT": [ { "tier": 1.0, @@ -33500,13 +34188,13 @@ "symbol": "NEAR/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -33516,136 +34204,136 @@ "tier": 2.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 100000.0, + "minNotional": 20000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "100000", - "notionalFloor": "10000", + "notionalCap": "200000", + "notionalFloor": "20000", "maintMarginRatio": "0.015", - "cum": "50.0" + "cum": "100.0" } }, { "tier": 3.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 500000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "500000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.02", - "cum": "550.0" + "cum": "1100.0" } }, { "tier": 4.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.025", - "cum": "3050.0" + "cum": "6100.0" } }, { "tier": 5.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 2000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.05", - "cum": "28050.0" + "cum": "56100.0" } }, { "tier": 6.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.1", - "cum": "278050.0" + "cum": "556100.0" } }, { "tier": 7.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, + "minNotional": 20000000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "12500000", - "notionalFloor": "10000000", + "notionalCap": "25000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.125", - "cum": "528050.0" + "cum": "1056100.0" } }, { "tier": 8.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 25000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "25000000", - "notionalFloor": "12500000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.25", - "cum": "2090550.0" + "cum": "4181100.0" } }, { "tier": 9.0, "symbol": "NEAR/USDT:USDT", "currency": "USDT", - "minNotional": 25000000.0, - "maxNotional": 50000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "25000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "8340550.0" + "cum": "16681100.0" } } ], @@ -35434,10 +36122,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -35451,10 +36139,10 @@ "minNotional": 5000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -35468,10 +36156,10 @@ "minNotional": 50000.0, "maxNotional": 900000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "900000", "notionalFloor": "50000", "maintMarginRatio": "0.05", @@ -35483,13 +36171,13 @@ "symbol": "OMG/USDT:USDT", "currency": "USDT", "minNotional": 900000.0, - "maxNotional": 2400000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2400000", + "notionalCap": "2000000", "notionalFloor": "900000", "maintMarginRatio": "0.1", "cum": "46300.0" @@ -35499,51 +36187,51 @@ "tier": 5.0, "symbol": "OMG/USDT:USDT", "currency": "USDT", - "minNotional": 2400000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 2100000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "3000000", - "notionalFloor": "2400000", + "notionalCap": "2100000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "106300.0" + "cum": "96300.0" } }, { "tier": 6.0, "symbol": "OMG/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 9000000.0, + "minNotional": 2100000.0, + "maxNotional": 2200000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "9000000", - "notionalFloor": "3000000", + "notionalCap": "2200000", + "notionalFloor": "2100000", "maintMarginRatio": "0.25", - "cum": "481300.0" + "cum": "358800.0" } }, { "tier": 7.0, "symbol": "OMG/USDT:USDT", "currency": "USDT", - "minNotional": 9000000.0, - "maxNotional": 15000000.0, + "minNotional": 2200000.0, + "maxNotional": 2300000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "9000000", + "notionalCap": "2300000", + "notionalFloor": "2200000", "maintMarginRatio": "0.5", - "cum": "2731300.0" + "cum": "908800.0" } } ], @@ -36400,10 +37088,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -36417,10 +37105,10 @@ "minNotional": 5000.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 16.0, + "maxLeverage": 9.0, "info": { "bracket": "2", - "initialLeverage": "16", + "initialLeverage": "9", "notionalCap": "10000", "notionalFloor": "5000", "maintMarginRatio": "0.015", @@ -36434,10 +37122,10 @@ "minNotional": 10000.0, "maxNotional": 30000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 14.0, + "maxLeverage": 8.0, "info": { "bracket": "3", - "initialLeverage": "14", + "initialLeverage": "8", "notionalCap": "30000", "notionalFloor": "10000", "maintMarginRatio": "0.02", @@ -36451,10 +37139,10 @@ "minNotional": 30000.0, "maxNotional": 60000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 12.0, + "maxLeverage": 7.0, "info": { "bracket": "4", - "initialLeverage": "12", + "initialLeverage": "7", "notionalCap": "60000", "notionalFloor": "30000", "maintMarginRatio": "0.025", @@ -36468,10 +37156,10 @@ "minNotional": 60000.0, "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxLeverage": 6.0, "info": { "bracket": "5", - "initialLeverage": "10", + "initialLeverage": "6", "notionalCap": "300000", "notionalFloor": "60000", "maintMarginRatio": "0.05", @@ -36517,6 +37205,161 @@ "symbol": "ORBS/USDT:USDT", "currency": "USDT", "minNotional": 750000.0, + "maxNotional": 900000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "900000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "ORBS/USDT:USDT", + "currency": "USDT", + "minNotional": 900000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "1000000", + "notionalFloor": "900000", + "maintMarginRatio": "0.5", + "cum": "350475.0" + } + } + ], + "ORCA/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "ORCA/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, "maxNotional": 1500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, @@ -36531,16 +37374,16 @@ }, { "tier": 9.0, - "symbol": "ORBS/USDT:USDT", + "symbol": "ORCA/USDT:USDT", "currency": "USDT", "minNotional": 1500000.0, - "maxNotional": 2000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", + "notionalCap": "3000000", "notionalFloor": "1500000", "maintMarginRatio": "0.5", "cum": "500475.0" @@ -37725,13 +38568,13 @@ "symbol": "PNUT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -37741,119 +38584,119 @@ "tier": 3.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "550.0" } }, { "tier": 4.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "3050.0" } }, { "tier": 5.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "28050.0" } }, { "tier": 6.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "278050.0" } }, { "tier": 7.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "528050.0" } }, { "tier": 8.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "2090550.0" } }, { "tier": 9.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "8340550.0" } } ], @@ -39668,6 +40511,161 @@ } } ], + "RAYSOL/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "RAYSOL/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "RDNT/USDT:USDT": [ { "tier": 1.0, @@ -42691,13 +43689,13 @@ "symbol": "SEI/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 60000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "60000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -42707,119 +43705,119 @@ "tier": 3.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "350.0" + "cum": "550.0" } }, { "tier": 4.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "1850.0" + "cum": "3050.0" } }, { "tier": 5.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "600000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "16850.0" + "cum": "28050.0" } }, { "tier": 6.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "166850.0" + "cum": "278050.0" } }, { "tier": 7.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "316850.0" + "cum": "528050.0" } }, { "tier": 8.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1254350.0" + "cum": "2090550.0" } }, { "tier": 9.0, "symbol": "SEI/USDT:USDT", "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5004350.0" + "cum": "8340550.0" } } ], @@ -44099,6 +45097,161 @@ } } ], + "SPX/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "SPX/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "SSV/USDT:USDT": [ { "tier": 1.0, @@ -45124,13 +46277,13 @@ "symbol": "STX/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 30000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "30000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -45140,119 +46293,119 @@ "tier": 3.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 150000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "150000", - "notionalFloor": "30000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "200.0" + "cum": "350.0" } }, { "tier": 4.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 300000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "300000", - "notionalFloor": "150000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "950.0" + "cum": "1850.0" } }, { "tier": 5.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 1500000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1500000", - "notionalFloor": "300000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "8450.0" + "cum": "16850.0" } }, { "tier": 6.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "83450.0" + "cum": "166850.0" } }, { "tier": 7.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 3750000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "3750000", - "notionalFloor": "3000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "158450.0" + "cum": "316850.0" } }, { "tier": 8.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 3750000.0, - "maxNotional": 7500000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "7500000", - "notionalFloor": "3750000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "627200.0" + "cum": "1254350.0" } }, { "tier": 9.0, "symbol": "STX/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "2502200.0" + "cum": "5004350.0" } } ], @@ -46865,13 +48018,13 @@ "symbol": "THE/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -46881,136 +48034,136 @@ "tier": 2.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", + "notionalCap": "40000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 30000.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "30000", - "notionalFloor": "10000", + "notionalCap": "200000", + "notionalFloor": "40000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "250.0" } }, { "tier": 4.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 60000.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "60000", - "notionalFloor": "30000", + "notionalCap": "400000", + "notionalFloor": "200000", "maintMarginRatio": "0.025", - "cum": "225.0" + "cum": "1250.0" } }, { "tier": 5.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 300000.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "60000", + "notionalCap": "2000000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "1725.0" + "cum": "11250.0" } }, { "tier": 6.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "600000", - "notionalFloor": "300000", + "notionalCap": "4000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "16725.0" + "cum": "111250.0" } }, { "tier": 7.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 750000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "600000", + "notionalCap": "5000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.125", - "cum": "31725.0" + "cum": "211250.0" } }, { "tier": 8.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "750000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "125475.0" + "cum": "836250.0" } }, { "tier": 9.0, "symbol": "THE/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "500475.0" + "cum": "3336250.0" } } ], @@ -49192,13 +50345,13 @@ "symbol": "UNI/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 80000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 40.0, "info": { "bracket": "3", "initialLeverage": "40", - "notionalCap": "80000", + "notionalCap": "100000", "notionalFloor": "50000", "maintMarginRatio": "0.015", "cum": "290.0" @@ -49208,119 +50361,119 @@ "tier": 4.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 300000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "300000", - "notionalFloor": "80000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "690.0" + "cum": "790.0" } }, { "tier": 5.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 900000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "5", "initialLeverage": "20", - "notionalCap": "900000", - "notionalFloor": "300000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "2190.0" + "cum": "3290.0" } }, { "tier": 6.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 900000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "900000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "24690.0" + "cum": "28290.0" } }, { "tier": 7.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "174690.0" + "cum": "278290.0" } }, { "tier": 8.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "324690.0" + "cum": "528290.0" } }, { "tier": 9.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 18000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "18000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1262190.0" + "cum": "2090790.0" } }, { "tier": 10.0, "symbol": "UNI/USDT:USDT", "currency": "USDT", - "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "18000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5762190.0" + "cum": "8340790.0" } } ], @@ -50152,6 +51305,161 @@ } } ], + "VIRTUAL/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" + } + }, + { + "tier": 5.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" + } + }, + { + "tier": 6.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" + } + }, + { + "tier": 7.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" + } + }, + { + "tier": 8.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "125475.0" + } + }, + { + "tier": 9.0, + "symbol": "VIRTUAL/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.5", + "cum": "500475.0" + } + } + ], "VOXEL/USDT:USDT": [ { "tier": 1.0, @@ -51677,14 +52985,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -51693,50 +53001,50 @@ "symbol": "XLM/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "150000", + "initialLeverage": "50", + "notionalCap": "100000", "notionalFloor": "10000", - "maintMarginRatio": "0.025", - "cum": "100.0" + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "symbol": "XLM/USDT:USDT", "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.03, - "maxLeverage": 15.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "15", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.03", - "cum": "850.0" + "initialLeverage": "25", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.02", + "cum": "550.0" } }, { "tier": 4.0, "symbol": "XLM/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.05", - "cum": "5850.0" + "notionalFloor": "500000", + "maintMarginRatio": "0.025", + "cum": "3050.0" } }, { @@ -51744,67 +53052,84 @@ "symbol": "XLM/USDT:USDT", "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.05", + "cum": "28050.0" + } + }, + { + "tier": 6.0, + "symbol": "XLM/USDT:USDT", + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "55850.0" + "cum": "278050.0" } }, { - "tier": 6.0, + "tier": 7.0, "symbol": "XLM/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "105850.0" + "cum": "528050.0" } }, { - "tier": 7.0, + "tier": 8.0, "symbol": "XLM/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "730850.0" + "cum": "2090550.0" } }, { - "tier": 8.0, + "tier": 9.0, "symbol": "XLM/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "3230850.0" + "cum": "8340550.0" } } ], @@ -53781,14 +55106,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -53797,15 +55122,15 @@ "symbol": "ZRX/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, @@ -53813,102 +55138,119 @@ "tier": 3.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "symbol": "ZRX/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "125.0" + "cum": "325.0" } }, { - "tier": 4.0, + "tier": 5.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "750.0" + "cum": "2825.0" } }, { - "tier": 5.0, + "tier": 6.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "10750.0" + "cum": "27825.0" } }, { - "tier": 6.0, + "tier": 7.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", + "notionalCap": "1250000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "20750.0" + "cum": "52825.0" } }, { - "tier": 7.0, + "tier": 8.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1250000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2500000", + "notionalFloor": "1250000", "maintMarginRatio": "0.25", - "cum": "83250.0" + "cum": "209075.0" } }, { - "tier": 8.0, + "tier": 9.0, "symbol": "ZRX/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "834075.0" } } ] From d2b4d1e183302cfeddb0f73b34bd6355c6a496c9 Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 12 Dec 2024 11:45:40 +0000 Subject: [PATCH 90/92] add more explanation regarding reducing position partially --- docs/strategy-callbacks.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 44d675055a9..51b55379059 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -782,6 +782,12 @@ The strategy is expected to return a negative stake_amount (in stake currency) f Returning the full owned stake at that point (`-trade.stake_amount`) results in a full exit. Returning a value more than the above (so remaining stake_amount would become negative) will result in the bot ignoring the signal. +For a partial exit, it's important to know that the formula used to calculate the amount of the coin for the partial exit order is `amount to be exited partially = negative_stake_amount * trade.amount / trade.stake_amount`, where `negative_stake_amount` is the value returned from the `adjust_trade_position` function. As seen in the formula, the formula don't care about current profit/loss of the position. It only care about `trade.amount` and `trade.stake_amount` which aren't affected by the price movement at all. + +For example, let's say you buy 2 SHITCOIN/USDT at open rate of 50, which means the trade's stake amount is 100 USDT. Now the price has rose to 200 and you want to sell half of it. In that case, you have to return -50% of trade.stake_amount (0.5 * 100 USDT) which equals to -50. The bot will calculate the amount it needed to sell, which is `50 * 2 / 100` which equals 1 SHITCOIN/USDT. If you return -200 (50% of 2 * 200), the bot will ignore it since trade.stake_amount is only 100 USDT but you asked it to sell 200 USDT which means you are asking it to sell 4 SHITCOIN/USDT. + +Back to the example above, since current rate is 200, the current USDT value of your trade is now 400 USDT. Let's say you want to partially sell 100 USDT to take out the initial investment and leave the profit in the trade in hope of the price keep rising. In that case, you have to do different approach. First, you need to calculate the exact amount you needed to sell. In this case, since you want to sell 100 USDT worth based of current rate, the exact amount you need to partially sell is `100 * 2 / 400` which equals 0.5 SHITCOIN/USDT. Since we know now the exact amount we want to sell (0.5), the value you need to return in the `adjust_trade_position` function is `-amount to be exited partially * trade.stake_amount / trade.amount`, which equals -25. The bot will sell 0.5 SHITCOIN/USDT, keeping 1.5 in trade. You will receive 100 USDT from the partial exit. + !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. From c00b248d1acf46d2dd1709a90e78b3fec1cbae29 Mon Sep 17 00:00:00 2001 From: Robert Davey Date: Thu, 12 Dec 2024 13:17:52 +0000 Subject: [PATCH 91/92] Add initial_state option to FAQ --- docs/faq.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 98b10689901..a41e641f674 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -40,6 +40,10 @@ This could be caused by the following reasons: * The installation did not complete successfully. * Please check the [Installation documentation](installation.md). +### The bot starts, but in STOPPED mode + +Make sure you set the `initial_state` config option to `"running"` in your config.json + ### I have waited 5 minutes, why hasn't the bot made any trades yet? * Depending on the buy strategy, the amount of whitelisted coins, the From 8cf3c7b82610405ba5dc8fc48ede6b7216c60334 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Dec 2024 20:08:27 +0100 Subject: [PATCH 92/92] chore: use FtPrecise for stake_amount_filled calculation closes #11073 --- freqtrade/persistence/trade_model.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e4979b438dc..c944554c575 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -172,12 +172,20 @@ def trade(self) -> "LocalTrade": @property def stake_amount(self) -> float: """Amount in stake currency used for this order""" - return self.safe_amount * self.safe_price / self.trade.leverage + return float( + FtPrecise(self.safe_amount) + * FtPrecise(self.safe_price) + / FtPrecise(self.trade.leverage) + ) @property def stake_amount_filled(self) -> float: """Filled Amount in stake currency used for this order""" - return self.safe_filled * self.safe_price / self.trade.leverage + return float( + FtPrecise(self.safe_filled) + * FtPrecise(self.safe_price) + / FtPrecise(self.trade.leverage) + ) def __repr__(self): return (