diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e7b11672..9ecd27cc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: - cron: '0 5 * * 4' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}" cancel-in-progress: true permissions: repository-projects: read @@ -77,6 +77,17 @@ jobs: # Allow failure for coveralls coveralls || true + - name: Check for repository changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "Repository is dirty, changes detected:" + git status + git diff + exit 1 + else + echo "Repository is clean, no changes detected." + fi + - name: Backtesting (multi) run: | cp config_examples/config_bittrex.example.json config.json @@ -174,6 +185,17 @@ jobs: run: | pytest --random-order + - name: Check for repository changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "Repository is dirty, changes detected:" + git status + git diff + exit 1 + else + echo "Repository is clean, no changes detected." + fi + - name: Backtesting run: | cp config_examples/config_bittrex.example.json config.json @@ -237,6 +259,18 @@ jobs: run: | pytest --random-order + - name: Check for repository changes + run: | + if (git status --porcelain) { + Write-Host "Repository is dirty, changes detected:" + git status + git diff + exit 1 + } + else { + Write-Host "Repository is clean, no changes detected." + } + - name: Backtesting run: | cp config_examples/config_bittrex.example.json config.json @@ -302,7 +336,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - name: Documentation build run: | diff --git a/build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl b/build_helpers/pyarrow-12.0.0-cp39-cp39-linux_armv7l.whl similarity index 59% rename from build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl rename to build_helpers/pyarrow-12.0.0-cp39-cp39-linux_armv7l.whl index a7ad80bdf..2a8d1ff51 100644 Binary files a/build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl and b/build_helpers/pyarrow-12.0.0-cp39-cp39-linux_armv7l.whl differ diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 76c175304..ef1a23401 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -18,7 +18,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, which means that dry/live runs will keep the latest 2 models on disk. Setting to 0 keeps all models. This parameter also accepts a boolean to maintain backwards compatibility.
**Datatype:** Integer.
Default: `2`. | `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`.
**Datatype:** Boolean.
Default: `False` (no models are saved). | `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)).
**Datatype:** Positive integer. -| `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)).
**Datatype:** Boolean.
Default: `False`. +| `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)). Beware that this is currently a naive approach to incremental learning, and it has a high probability of overfitting/getting stuck in local minima while the market moves away from your model. We have the connections here primarily for experimental purposes and so that it is ready for more mature approaches to continual learning in chaotic systems like the crypto market.
**Datatype:** Boolean.
Default: `False`. | `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file.
**Datatype:** Boolean.
Default: `False` | `data_kitchen_thread_count` |
Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI)
**Datatype:** Positive integer. diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 962827348..547bb13eb 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -135,7 +135,14 @@ Parameter details can be found [here](freqai-parameter-table.md), but in general ## Creating a custom reward function -As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but users are encouraged to create their own custom reinforcement learning model class (see below) and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated: +!!! danger "Not for production" + Warning! + The reward function provided with the Freqtrade source code is a showcase of functionality designed to show/test as many possible environment control features as possible. It is also designed to run quickly on small computers. This is a benchmark, it is *not* for live production. Please beware that you will need to create your own custom_reward() function or use a template built by other users outside of the Freqtrade source code. + +As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but this is *not* designed for production. Users *must* create their own custom reinforcement learning model class or use a pre-built one from outside the Freqtrade source code and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated: + +!!! note "Hint" + The best reward functions are ones that are continuously differentiable, and well scaled. In other words, adding a single large negative penalty to a rare event is not a good idea, and the neural net will not be able to learn that function. Instead, it is better to add a small negative penalty to a common event. This will help the agent learn faster. Not only this, but you can help improve the continuity of your rewards/penalties by having them scale with severity according to some linear/exponential functions. In other words, you'd slowly scale the penalty as the duration of the trade increases. This is better than a single large penalty occuring at a single point in time. ```python from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner @@ -169,6 +176,11 @@ As you begin to modify the strategy and the prediction model, you will quickly r User made custom environment. This class inherits from BaseEnvironment and gym.env. Users can override any functions from those parent classes. Here is an example of a user customized `calculate_reward()` function. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: # first, penalize if the action is not valid diff --git a/docs/freqai-running.md b/docs/freqai-running.md index f3ccc546f..47d2ec4b3 100644 --- a/docs/freqai-running.md +++ b/docs/freqai-running.md @@ -131,6 +131,9 @@ You can choose to adopt a continual learning scheme by setting `"continual_learn ???+ danger "Continual learning enforces a constant parameter space" Since `continual_learning` means that the model parameter space *cannot* change between trainings, `principal_component_analysis` is automatically disabled when `continual_learning` is enabled. Hint: PCA changes the parameter space and the number of features, learn more about PCA [here](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis). +???+ danger "Experimental functionality" + Beware that this is currently a naive approach to incremental learning, and it has a high probability of overfitting/getting stuck in local minima while the market moves away from your model. We have the mechanics available in FreqAI primarily for experimental purposes and so that it is ready for more mature approaches to continual learning in chaotic systems like the crypto market. + ## Hyperopt You can hyperopt using the same command as for [typical Freqtrade hyperopt](hyperopt.md): diff --git a/docs/freqai.md b/docs/freqai.md index ef8efb840..02b723a20 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -32,7 +32,10 @@ The easiest way to quickly test FreqAI is to run it in dry mode with the followi freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates ``` -You will see the boot-up process of automatic data downloading, followed by simultaneous training and trading. +You will see the boot-up process of automatic data downloading, followed by simultaneous training and trading. + +!!! danger "Not for production" + The example strategy provided with the Freqtrade source code is designed for showcasing/testing a wide variety of FreqAI features. It is also designed to run on small computers so that it can be used as a benchmark between developers and users. It is *not* designed to be run in production. An example strategy, prediction model, and config to use as a starting points can be found in `freqtrade/templates/FreqaiExampleStrategy.py`, `freqtrade/freqai/prediction_models/LightGBMRegressor.py`, and @@ -69,11 +72,7 @@ pip install -r requirements-freqai.txt ``` !!! Note - Catboost will not be installed on arm devices (raspberry, Mac M1, ARM based VPS, ...), since it does not provide wheels for this platform. - -!!! Note "python 3.11" - Some dependencies (Catboost, Torch) currently don't support python 3.11. Freqtrade therefore only supports python 3.10 for these models/dependencies. - Tests involving these dependencies are skipped on 3.11. + Catboost will not be installed on low-powered arm devices (raspberry), since it does not provide wheels for this platform. ### Usage with docker diff --git a/docs/rest-api.md b/docs/rest-api.md index 860a44499..5b33bfa6f 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -134,7 +134,9 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `reload_config` | Reloads the configuration file. | `trades` | List last trades. Limited to 500 trades per call. | `trade/` | Get specific trade. -| `delete_trade ` | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `trade/` | DELETE - Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `trade//open-order` | DELETE - Cancel open order for this trade. +| `trade//reload` | GET - Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange. | `show_config` | Shows part of the current configuration with relevant settings to operation. | `logs` | Shows last log messages. | `status` | Lists all open trades. diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index e6017e271..1b36c60ad 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -187,6 +187,7 @@ official commands. You can ask at any moment for help with `/help`. | `/forcelong [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`force_entry_enable` must be set to True) | `/forceshort [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`force_entry_enable` must be set to True) | `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `/reload_trade ` | Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange. | `/cancel_open_order | /coo ` | Cancel an open order for a trade. | **Metrics** | | `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index bcef1c252..ed1571002 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -52,7 +52,7 @@ def start_download_data(args: Dict[str, Any]) -> None: pairs_not_available: List[str] = [] # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) markets = [p for p, m in exchange.markets.items() if market_is_active(m) or config.get('include_inactive')] @@ -125,7 +125,7 @@ def start_convert_trades(args: Dict[str, Any]) -> None: "Please check the documentation on how to configure this.") # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) # Manual validations of relevant settings if not config['exchange'].get('skip_pair_validation', False): exchange.validate_pairs(config['pairs']) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 4e0623081..3358f8cc8 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -114,7 +114,7 @@ def start_list_timeframes(args: Dict[str, Any]) -> None: config['timeframe'] = None # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) if args['print_one_column']: print('\n'.join(exchange.timeframes)) @@ -133,7 +133,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) # Init exchange - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) # By default only active pairs/markets are to be shown active_only = not args.get('list_pairs_all', False) diff --git a/freqtrade/commands/pairlist_commands.py b/freqtrade/commands/pairlist_commands.py index 9f7a5958e..a815cd5f3 100644 --- a/freqtrade/commands/pairlist_commands.py +++ b/freqtrade/commands/pairlist_commands.py @@ -18,7 +18,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None: from freqtrade.plugins.pairlistmanager import PairListManager config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + exchange = ExchangeResolver.load_exchange(config, validate=False) quote_currencies = args.get('quote_currencies') if not quote_currencies: diff --git a/freqtrade/enums/exittype.py b/freqtrade/enums/exittype.py index b025230ba..c21b62667 100644 --- a/freqtrade/enums/exittype.py +++ b/freqtrade/enums/exittype.py @@ -15,6 +15,7 @@ class ExitType(Enum): EMERGENCY_EXIT = "emergency_exit" CUSTOM_EXIT = "custom_exit" PARTIAL_EXIT = "partial_exit" + SOLD_ON_EXCHANGE = "sold_on_exchange" NONE = "" def __str__(self): diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 0b9be0f55..0f252f63e 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -1,4 +1,118 @@ { + "1000FLOKI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "800000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "15650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 800000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.125", + "cum": "35650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "160650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "910650.0" + } + } + ], "1000LUNC/BUSD:BUSD": [ { "tier": 1.0, @@ -211,6 +325,120 @@ } } ], + "1000PEPE/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "30650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "70650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1820650.0" + } + } + ], "1000SHIB/BUSD:BUSD": [ { "tier": 1.0, @@ -2174,10 +2402,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -2190,10 +2418,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -2206,10 +2434,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -2252,13 +2480,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "1200000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -4821,6 +5049,120 @@ } } ], + "BLUR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "30650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "70650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1820650.0" + } + } + ], "BLZ/USDT:USDT": [ { "tier": 1.0, @@ -8544,10 +8886,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", @@ -8560,10 +8902,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 8.0, "info": { "bracket": "2", - "initialLeverage": "15", + "initialLeverage": "8", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -8576,10 +8918,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", @@ -8638,13 +8980,13 @@ "tier": 7.0, "currency": "BUSD", "minNotional": 3000000.0, - "maxNotional": 8000000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "8000000", + "notionalCap": "4000000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", "cum": "949400.0" @@ -9041,6 +9383,120 @@ } } ], + "EDU/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "EGLD/USDT:USDT": [ { "tier": 1.0, @@ -9552,10 +10008,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -9568,10 +10024,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 7.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "7", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -9584,10 +10040,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -9630,13 +10086,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "1500000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -9805,6 +10261,168 @@ } } ], + "ETH/BTC:BTC": [ + { + "tier": 1.0, + "currency": "BTC", + "minNotional": 0.0, + "maxNotional": 5.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "5", + "notionalFloor": "0", + "maintMarginRatio": "0.005", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "BTC", + "minNotional": 5.0, + "maxNotional": 10.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10", + "notionalFloor": "5", + "maintMarginRatio": "0.006", + "cum": "0.005" + } + }, + { + "tier": 3.0, + "currency": "BTC", + "minNotional": 10.0, + "maxNotional": 100.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "100", + "notionalFloor": "10", + "maintMarginRatio": "0.01", + "cum": "0.045" + } + }, + { + "tier": 4.0, + "currency": "BTC", + "minNotional": 100.0, + "maxNotional": 250.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "250", + "notionalFloor": "100", + "maintMarginRatio": "0.02", + "cum": "1.045" + } + }, + { + "tier": 5.0, + "currency": "BTC", + "minNotional": 250.0, + "maxNotional": 800.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "800", + "notionalFloor": "250", + "maintMarginRatio": "0.025", + "cum": "2.295" + } + }, + { + "tier": 6.0, + "currency": "BTC", + "minNotional": 800.0, + "maxNotional": 1500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 8.0, + "info": { + "bracket": "6", + "initialLeverage": "8", + "notionalCap": "1500", + "notionalFloor": "800", + "maintMarginRatio": "0.05", + "cum": "22.295" + } + }, + { + "tier": 7.0, + "currency": "BTC", + "minNotional": 1500.0, + "maxNotional": 2000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "7", + "initialLeverage": "5", + "notionalCap": "2000", + "notionalFloor": "1500", + "maintMarginRatio": "0.1", + "cum": "97.295" + } + }, + { + "tier": 8.0, + "currency": "BTC", + "minNotional": 2000.0, + "maxNotional": 3000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "3000", + "notionalFloor": "2000", + "maintMarginRatio": "0.125", + "cum": "147.295" + } + }, + { + "tier": 9.0, + "currency": "BTC", + "minNotional": 3000.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "5000", + "notionalFloor": "3000", + "maintMarginRatio": "0.25", + "cum": "522.295" + } + }, + { + "tier": 10.0, + "currency": "BTC", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.5", + "cum": "1772.295" + } + } + ], "ETH/BUSD:BUSD": [ { "tier": 1.0, @@ -10364,10 +10982,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 8.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "8", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -10380,10 +10998,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 7.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "7", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -10396,10 +11014,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "6", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -10442,13 +11060,13 @@ "tier": 6.0, "currency": "BUSD", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -13341,6 +13959,120 @@ } } ], + "IDEX/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "IMX/USDT:USDT": [ { "tier": 1.0, @@ -13492,13 +14224,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 600000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "600000", + "notionalCap": "1200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -13507,65 +14239,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1600000.0, + "minNotional": 1200000.0, + "maxNotional": 3200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "600000", + "notionalCap": "3200000", + "notionalFloor": "1200000", "maintMarginRatio": "0.1", - "cum": "30650.0" + "cum": "60650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 3200000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "4000000", + "notionalFloor": "3200000", "maintMarginRatio": "0.125", - "cum": "70650.0" + "cum": "140650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 6000000.0, + "minNotional": 4000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "6000000", - "notionalFloor": "2000000", + "notionalCap": "12000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.25", - "cum": "320650.0" + "cum": "640650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1820650.0" + "cum": "3640650.0" } } ], @@ -15562,13 +16294,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "200000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -15577,65 +16309,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "30650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "cum": "70650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "320650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "1820650.0" } } ], @@ -17746,10 +18478,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -17762,10 +18494,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -17778,10 +18510,10 @@ "minNotional": 25000.0, "maxNotional": 900000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "900000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -18202,10 +18934,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -18218,10 +18950,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -18232,13 +18964,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "300000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18247,33 +18979,33 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 300000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "800000", + "notionalFloor": "300000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "15650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 800000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "800000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "35650.0" } }, { @@ -18281,15 +19013,31 @@ "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "160650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "910650.0" } } ], @@ -18815,6 +19563,120 @@ } } ], + "RAD/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "RAY/USDT:USDT": [ { "tier": 1.0, @@ -18950,13 +19812,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "200000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18965,65 +19827,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "30650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "cum": "70650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "320650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "1820650.0" } } ], @@ -21412,13 +22274,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", "cum": "75.0" @@ -21427,39 +22289,39 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 400000.0, + "minNotional": 50000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "400000", - "notionalFloor": "25000", + "notionalCap": "600000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "700.0" + "cum": "1325.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "400000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "20700.0" + "cum": "31325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 1600000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -21467,9 +22329,9 @@ "bracket": "5", "initialLeverage": "4", "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "45700.0" + "cum": "71325.0" } }, { @@ -21485,7 +22347,7 @@ "notionalCap": "6000000", "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "295700.0" + "cum": "321325.0" } }, { @@ -21501,7 +22363,137 @@ "notionalCap": "10000000", "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "1795700.0" + "cum": "1821325.0" + } + } + ], + "SUI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "300.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "7800.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "37800.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "77800.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "327800.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.5", + "cum": "1827800.0" } } ], @@ -22759,6 +23751,120 @@ } } ], + "UMA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "23150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], "UNFI/USDT:USDT": [ { "tier": 1.0, @@ -24818,10 +25924,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -24834,10 +25940,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -24848,13 +25954,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -24863,49 +25969,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "30700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "70700.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "320700.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1820700.0" } } ], diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 42a7094ba..e60207573 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -4,6 +4,7 @@ import time from functools import wraps from typing import Any, Callable, Optional, TypeVar, cast, overload +from freqtrade.constants import Config from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError from freqtrade.mixins import LoggingMixin @@ -84,10 +85,11 @@ EXCHANGE_HAS_OPTIONAL = [ # 'fetchPositions', # Futures trading # 'fetchLeverageTiers', # Futures initialization # 'fetchMarketLeverageTiers', # Futures initialization + # 'fetchOpenOrders', 'fetchClosedOrders', # 'fetchOrders', # Refinding balance... ] -def remove_credentials(config) -> None: +def remove_credentials(config: Config) -> None: """ Removes exchange keys from the configuration and specifies dry-run Used for backtesting / hyperopt / edge and utils. @@ -95,6 +97,7 @@ def remove_credentials(config) -> None: """ if config.get('dry_run', False): config['exchange']['key'] = '' + config['exchange']['apiKey'] = '' config['exchange']['secret'] = '' config['exchange']['password'] = '' config['exchange']['uid'] = '' diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 5273030ab..bb9c7c1b3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -92,7 +92,7 @@ class Exchange: # TradingMode.SPOT always supported and not required in this list ] - def __init__(self, config: Config, validate: bool = True, + def __init__(self, config: Config, *, validate: bool = True, load_leverage_tiers: bool = False) -> None: """ Initializes this module with the given config, @@ -1432,6 +1432,47 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + @retrier(retries=0) + def fetch_orders(self, pair: str, since: datetime) -> List[Dict]: + """ + Fetch all orders for a pair "since" + :param pair: Pair for the query + :param since: Starting time for the query + """ + if self._config['dry_run']: + return [] + + def fetch_orders_emulate() -> List[Dict]: + orders = [] + if self.exchange_has('fetchClosedOrders'): + orders = self._api.fetch_closed_orders(pair, since=since_ms) + if self.exchange_has('fetchOpenOrders'): + orders_open = self._api.fetch_open_orders(pair, since=since_ms) + orders.extend(orders_open) + return orders + + try: + since_ms = int((since.timestamp() - 10) * 1000) + if self.exchange_has('fetchOrders'): + try: + orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + except ccxt.NotSupported: + # Some exchanges don't support fetchOrders + # attempt to fetch open and closed orders separately + orders = fetch_orders_emulate() + else: + orders = fetch_orders_emulate() + self._log_exchange_response('fetch_orders', orders) + orders = [self._order_contracts_to_amount(o) for o in orders] + return orders + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not fetch positions due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + @retrier def fetch_trading_fees(self) -> Dict[str, Any]: """ diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py index 08bb93347..7c83a7e42 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -306,6 +306,12 @@ class BaseEnvironment(gym.Env): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index e2c0f5fda..3c6f2c142 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -371,6 +371,12 @@ class BaseReinforcementLearningModel(IFreqaiModel): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 1669d1483..8625d88ff 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -622,9 +622,11 @@ class IFreqaiModel(ABC): strategy, corr_dataframes, base_dataframes, pair ) - new_trained_timerange = dk.buffer_timerange(new_trained_timerange) + trained_timestamp = new_trained_timerange.stopts - unfiltered_dataframe = dk.slice_dataframe(new_trained_timerange, unfiltered_dataframe) + buffered_timerange = dk.buffer_timerange(new_trained_timerange) + + unfiltered_dataframe = dk.slice_dataframe(buffered_timerange, unfiltered_dataframe) # find the features indicated by strategy and store in datakitchen dk.find_features(unfiltered_dataframe) @@ -632,8 +634,8 @@ class IFreqaiModel(ABC): model = self.train(unfiltered_dataframe, pair, dk) - self.dd.pair_dict[pair]["trained_timestamp"] = new_trained_timerange.stopts - dk.set_new_model_names(pair, new_trained_timerange.stopts) + self.dd.pair_dict[pair]["trained_timestamp"] = trained_timestamp + dk.set_new_model_names(pair, trained_timestamp) self.dd.save_data(model, pair, dk) if self.plot_features: diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py index ea7981405..b29d20112 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py @@ -74,16 +74,17 @@ class PyTorchMLPClassifier(BasePyTorchClassifier): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.CrossEntropyLoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchModelTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - model_meta_data={"class_names": class_names}, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchModelTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + model_meta_data={"class_names": class_names}, + device=self.device, + data_convertor=self.data_convertor, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py index 64f0f4b03..6e1270102 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py @@ -69,15 +69,16 @@ class PyTorchMLPRegressor(BasePyTorchRegressor): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.MSELoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchModelTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchModelTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + device=self.device, + data_convertor=self.data_convertor, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py index e760f6e68..5e84ada72 100644 --- a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py @@ -75,17 +75,18 @@ class PyTorchTransformerRegressor(BasePyTorchRegressor): model.to(self.device) optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) criterion = torch.nn.MSELoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchTransformerTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - device=self.device, - init_model=init_model, - data_convertor=self.data_convertor, - window_size=self.window_size, - **self.trainer_kwargs, - ) + # check if continual_learning is activated, and retreive the model to continue training + trainer = self.get_init_model(dk.pair) + if trainer is None: + trainer = PyTorchTransformerTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + device=self.device, + data_convertor=self.data_convertor, + window_size=self.window_size, + **self.trainer_kwargs, + ) trainer.fit(data_dictionary, self.splits) return trainer diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner.py b/freqtrade/freqai/prediction_models/ReinforcementLearner.py index a5c2e12b5..8c9d9bdef 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner.py @@ -97,6 +97,12 @@ class ReinforcementLearner(BaseReinforcementLearningModel): """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. + :param action: int = The action made by the agent for the current candle. :return: float = the reward to give to the agent for current step (used for optimization diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py index a3b0d9b9c..a9310a182 100644 --- a/freqtrade/freqai/torch/PyTorchModelTrainer.py +++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py @@ -25,7 +25,6 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): optimizer: Optimizer, criterion: nn.Module, device: str, - init_model: Dict, data_convertor: PyTorchDataConvertor, model_meta_data: Dict[str, Any] = {}, window_size: int = 1, @@ -56,8 +55,6 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): self.max_n_eval_batches: Optional[int] = kwargs.get("max_n_eval_batches", None) self.data_convertor = data_convertor self.window_size: int = window_size - if init_model: - self.load_from_checkpoint(init_model) def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]): """ diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5e1e7d5f8..436564b42 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1,9 +1,9 @@ """ Freqtrade is the main module of this bot. It contains the class Freqtrade() """ -import copy import logging import traceback +from copy import deepcopy from datetime import datetime, time, timedelta, timezone from math import isclose from threading import Lock @@ -70,7 +70,7 @@ class FreqtradeBot(LoggingMixin): validate_config_consistency(config) self.exchange = ExchangeResolver.load_exchange( - self.config['exchange']['name'], self.config, load_leverage_tiers=True) + self.config, load_leverage_tiers=True) init_db(self.config['db_url']) @@ -451,6 +451,42 @@ class FreqtradeBot(LoggingMixin): except ExchangeError: logger.warning(f"Error updating {order.order_id}.") + def handle_onexchange_order(self, trade: Trade): + """ + Try refinding a order that is not in the database. + Only used balance disappeared, which would make exiting impossible. + """ + try: + orders = self.exchange.fetch_orders(trade.pair, trade.open_date_utc) + for order in orders: + trade_order = [o for o in trade.orders if o.order_id == order['id']] + if trade_order: + continue + logger.info(f"Found previously unknown order {order['id']} for {trade.pair}.") + + order_obj = Order.parse_from_ccxt_object(order, trade.pair, order['side']) + order_obj.order_filled_date = datetime.fromtimestamp( + safe_value_fallback(order, 'lastTradeTimestamp', 'timestamp') // 1000, + tz=timezone.utc) + trade.orders.append(order_obj) + # TODO: how do we handle open_order_id ... + Trade.commit() + prev_exit_reason = trade.exit_reason + trade.exit_reason = ExitType.SOLD_ON_EXCHANGE.value + self.update_trade_state(trade, order['id'], order) + + logger.info(f"handled order {order['id']}") + if not trade.is_open: + # Trade was just closed + trade.close_date = order_obj.order_filled_date + Trade.commit() + break + else: + trade.exit_reason = prev_exit_reason + Trade.commit() + + except ExchangeError: + logger.warning("Error finding onexchange order") # # BUY / enter positions / open trades logic and methods # @@ -461,7 +497,7 @@ class FreqtradeBot(LoggingMixin): """ trades_created = 0 - whitelist = copy.deepcopy(self.active_pair_whitelist) + whitelist = deepcopy(self.active_pair_whitelist) if not whitelist: self.log_once("Active pair whitelist is empty.", logger.info) return trades_created @@ -1034,6 +1070,13 @@ class FreqtradeBot(LoggingMixin): """ trades_closed = 0 for trade in trades: + + if not self.wallets.check_exit_amount(trade): + logger.warning( + f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. ' + 'Trying to recover.') + self.handle_onexchange_order(trade) + try: try: if (self.strategy.order_types.get('stoploss_on_exchange') and @@ -1536,13 +1579,13 @@ class FreqtradeBot(LoggingMixin): # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() if self.trading_mode == TradingMode.FUTURES: + # A safe exit amount isn't needed for futures, you can just exit/close the position return amount trade_base_currency = self.exchange.get_pair_base_currency(pair) wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: - # A safe exit amount isn't needed for futures, you can just exit/close the position return amount elif wallet_amount > amount * 0.98: logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.") diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 622fe4444..d77fc469b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -89,8 +89,7 @@ class Backtesting: self.rejected_df: Dict[str, Dict] = {} self._exchange_name = self.config['exchange']['name'] - self.exchange = ExchangeResolver.load_exchange( - self._exchange_name, self.config, load_leverage_tiers=True) + self.exchange = ExchangeResolver.load_exchange(self.config, load_leverage_tiers=True) self.dataprovider = DataProvider(self.config, self.exchange) if self.config.get('strategy_list'): diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 2eb1c53f5..07c54d720 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -32,7 +32,7 @@ class EdgeCli: # Ensure using dry-run self.config['dry_run'] = True self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT - self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) + self.exchange = ExchangeResolver.load_exchange(self.config) self.strategy = StrategyResolver.load_strategy(self.config) self.strategy.dp = DataProvider(config, self.exchange) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index e415c4911..7fd20f041 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -633,7 +633,7 @@ def load_and_plot_trades(config: Config): """ strategy = StrategyResolver.load_strategy(config) - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) + exchange = ExchangeResolver.load_exchange(config) IStrategy.dp = DataProvider(config, exchange) strategy.ft_bot_start() strategy.bot_loop_start(datetime.now(timezone.utc)) @@ -678,7 +678,7 @@ def plot_profit(config: Config) -> None: if 'timeframe' not in config: raise OperationalException('Timeframe must be set in either config or via --timeframe.') - exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) + exchange = ExchangeResolver.load_exchange(config) plot_elements = init_plotscript(config, list(exchange.markets)) trades = plot_elements['trades'] # Filter trades to relevant pairs diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 54a488e8d..e888028dc 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -19,13 +19,14 @@ class ExchangeResolver(IResolver): object_type = Exchange @staticmethod - def load_exchange(exchange_name: str, config: Config, validate: bool = True, + def load_exchange(config: Config, validate: bool = True, load_leverage_tiers: bool = False) -> Exchange: """ Load the custom class from config parameter :param exchange_name: name of the Exchange to load :param config: configuration dictionary """ + exchange_name: str = config['exchange']['name'] # Map exchange name to avoid duplicate classes for identical exchanges exchange_name = MAP_EXCHANGE_CHILDCLASS.get(exchange_name, exchange_name) exchange_name = exchange_name.title() diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 279336c07..ccbcbb94f 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -44,7 +44,8 @@ logger = logging.getLogger(__name__) # 2.24: Add cancel_open_order endpoint # 2.25: Add several profit values to /status endpoint # 2.26: increase /balance output -API_VERSION = 2.26 +# 2.27: Add /trades//reload endpoint +API_VERSION = 2.27 # Public API, requires no auth. router_public = APIRouter() @@ -127,11 +128,17 @@ def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)): @router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading']) -def cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): +def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): rpc._rpc_cancel_open_order(tradeid) return rpc._rpc_trade_status([tradeid])[0] +@router.get('/trades/{tradeid}/reload', response_model=OpenTradeSchema, tags=['trading']) +def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)): + rpc._rpc_reload_trade_from_exchange(tradeid) + return rpc._rpc_trade_status([tradeid])[0] + + # TODO: Missing response model @router.get('/edge', tags=['info']) def edge(rpc: RPC = Depends(get_rpc)): diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index f5b1bcd74..bfc1e698c 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -46,7 +46,7 @@ def get_exchange(config=Depends(get_config)): if not ApiServer._exchange: from freqtrade.resolvers import ExchangeResolver ApiServer._exchange = ExchangeResolver.load_exchange( - config['exchange']['name'], config, load_leverage_tiers=False) + config, load_leverage_tiers=False) return ApiServer._exchange diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 818ef16ff..4a67adf29 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -740,6 +740,18 @@ class RPC: return {'status': 'No more entries will occur from now. Run /reload_config to reset.'} + def _rpc_reload_trade_from_exchange(self, trade_id: int) -> Dict[str, str]: + """ + Handler for reload_trade_from_exchange. + Reloads a trade from it's orders, should manual interaction have happened. + """ + trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() + if not trade: + raise RPCException(f"Could not find trade with id {trade_id}.") + + self._freqtrade.handle_onexchange_order(trade) + return {'status': 'Reloaded from orders from exchange'} + def __exec_force_exit(self, trade: Trade, ordertype: Optional[str], amount: Optional[float] = None) -> None: # Check if there is there is an open order diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index caa8715ac..a5b948f69 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -196,6 +196,7 @@ class Telegram(RPCHandler): self._force_enter, order_side=SignalDirection.LONG)), CommandHandler('forceshort', partial( self._force_enter, order_side=SignalDirection.SHORT)), + CommandHandler('reload_trade', self._reload_trade_from_exchange), CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), CommandHandler(['coo', 'cancel_open_order'], self._cancel_open_order), @@ -1074,6 +1075,17 @@ class Telegram(RPCHandler): msg = self._rpc._rpc_stopentry() await self._send_msg(f"Status: `{msg['status']}`") + @authorized_only + async def _reload_trade_from_exchange(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /reload_trade . + """ + if not context.args or len(context.args) == 0: + raise RPCException("Trade-id not set.") + trade_id = int(context.args[0]) + msg = self._rpc._rpc_reload_trade_from_exchange(trade_id) + await self._send_msg(f"Status: `{msg['status']}`") + @authorized_only async def _force_exit(self, update: Update, context: CallbackContext) -> None: """ @@ -1561,6 +1573,7 @@ class Telegram(RPCHandler): "*/fx |all:* `Alias to /forceexit`\n" f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" + "*/reload_trade :* `Relade trade from exchange Orders`\n" "*/cancel_open_order :* `Cancels open orders for trade. " "Only valid when the trade has open orders.`\n" "*/coo |all:* `Alias to /cancel_open_order`\n" diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 493ea17f3..347efdda0 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -15,12 +15,15 @@ logger = logging.getLogger(__name__) class FreqaiExampleStrategy(IStrategy): """ Example strategy showing how the user connects their own - IFreqaiModel to the strategy. Namely, the user uses: - self.freqai.start(dataframe, metadata) + IFreqaiModel to the strategy. - to make predictions on their data. feature_engineering_*() automatically - generate the variety of features indicated by the user in the - canonical freqtrade configuration file under config['freqai']. + Warning! This is a showcase of functionality, + which means that it is designed to show various functions of FreqAI + and it runs on all computers. We use this showcase to help users + understand how to build a strategy, and we use it as a benchmark + to help debug possible problems. + + This means this is *not* meant to be run live in production. """ minimal_roi = {"0": 0.1, "240": -1} diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 6f86398f3..9a33d1fb1 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -181,6 +181,35 @@ class Wallets: def get_all_positions(self) -> Dict[str, PositionWallet]: return self._positions + def _check_exit_amount(self, trade: Trade) -> bool: + if trade.trading_mode != TradingMode.FUTURES: + # Slightly higher offset than in safe_exit_amount. + wallet_amount: float = self.get_total(trade.safe_base_currency) * (2 - 0.981) + else: + # wallet_amount: float = self.wallets.get_free(trade.safe_base_currency) + position = self._positions.get(trade.pair) + if position is None: + # We don't own anything :O + return False + wallet_amount = position.position + + if wallet_amount >= trade.amount: + return True + return False + + def check_exit_amount(self, trade: Trade) -> bool: + """ + Checks if the exit amount is available in the wallet. + :param trade: Trade to check + :return: True if the exit amount is available, False otherwise + """ + if not self._check_exit_amount(trade): + # Update wallets just to make sure + self.update() + return self._check_exit_amount(trade) + + return True + def get_starting_balance(self) -> float: """ Retrieves starting balance - based on either available capital, diff --git a/requirements-freqai.txt b/requirements-freqai.txt index e5bc23d56..ad069ade2 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,7 +5,8 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 -catboost==1.1.1; platform_machine != 'aarch64' and 'arm' not in platform_machine and python_version < '3.11' +catboost==1.1.1; sys_platform == 'darwin' and python_version < '3.9' +catboost==1.2; 'arm' not in platform_machine and (sys_platform != 'darwin' or python_version >= '3.9') lightgbm==3.3.5 xgboost==1.7.5 tensorboard==2.13.0 diff --git a/requirements.txt b/requirements.txt index baa7e4d26..448bf1fc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ tables==3.8.0 blosc==1.11.1 joblib==1.2.0 rich==13.3.5 -pyarrow==11.0.0; platform_machine != 'armv7l' +pyarrow==12.0.0; platform_machine != 'armv7l' # find first, C search in arrays py_find_1st==1.1.5 diff --git a/setup.py b/setup.py index 0421e307a..b59e98ae8 100644 --- a/setup.py +++ b/setup.py @@ -69,9 +69,9 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=2.6.26', + 'ccxt>=3.0.0', 'SQLAlchemy>=2.0.6', - 'python-telegram-bot>=13.4', + 'python-telegram-bot>=20.1', 'arrow>=0.17.0', 'cachetools', 'requests', diff --git a/setup.sh b/setup.sh index cb982b368..84f804021 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,7 @@ function check_installed_python() { exit 2 fi - for v in 10 9 8 + for v in 11 10 9 8 do PYTHON="python3.${v}" which $PYTHON @@ -258,7 +258,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.8 - 3.10, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.8 - 3.11, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi diff --git a/tests/conftest.py b/tests/conftest.py index 1c737b3aa..70d15c6df 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -181,7 +181,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='binance', patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) config['exchange']['name'] = id try: - exchange = ExchangeResolver.load_exchange(id, config, load_leverage_tiers=True) + exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True) except ImportError: exchange = Exchange(config) return exchange @@ -411,6 +411,14 @@ def patch_gc(mocker) -> None: mocker.patch("freqtrade.main.gc_set_threshold") +@pytest.fixture(autouse=True) +def user_dir(mocker, tmpdir) -> Path: + user_dir = Path(tmpdir) / "user_data" + mocker.patch('freqtrade.configuration.configuration.create_userdata_dir', + return_value=user_dir) + return user_dir + + @pytest.fixture(autouse=True) def patch_coingekko(mocker) -> None: """ @@ -485,7 +493,6 @@ def get_default_conf(testdatadir): }, "exchange": { "name": "binance", - "enabled": True, "key": "key", "secret": "secret", "pair_whitelist": [ diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py index 367ad8394..810e2c53b 100644 --- a/tests/data/test_entryexitanalysis.py +++ b/tests/data/test_entryexitanalysis.py @@ -18,8 +18,9 @@ def entryexitanalysis_cleanup() -> None: Backtesting.cleanup() -def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmpdir, capsys): +def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, user_dir, capsys): caplog.set_level(logging.INFO) + (user_dir / 'backtest_results').mkdir(parents=True, exist_ok=True) default_conf.update({ "use_exit_signal": True, @@ -80,7 +81,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp 'backtesting', '--config', 'config.json', '--datadir', str(testdatadir), - '--user-data-dir', str(tmpdir), + '--user-data-dir', str(user_dir), '--timeframe', '5m', '--timerange', '1515560100-1517287800', '--export', 'signals', @@ -98,7 +99,7 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp 'backtesting-analysis', '--config', 'config.json', '--datadir', str(testdatadir), - '--user-data-dir', str(tmpdir), + '--user-data-dir', str(user_dir), ] # test group 0 and indicator list diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 60855ca54..6f5987202 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -302,7 +302,7 @@ def exchange(request, exchange_conf): exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) exchange_conf['exchange']['name'] = request.param exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] - exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) + exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True) yield exchange, request.param @@ -330,7 +330,7 @@ def exchange_futures(request, exchange_conf, class_mocker): class_mocker.patch(f'{EXMS}.cache_leverage_tiers') exchange = ExchangeResolver.load_exchange( - request.param, exchange_conf, validate=True, load_leverage_tiers=True) + exchange_conf, validate=True, load_leverage_tiers=True) yield exchange, request.param diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 399442b08..ebbecdad0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -228,27 +228,30 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch(f'{EXMS}.validate_timeframes') mocker.patch(f'{EXMS}.validate_stakecurrency') mocker.patch(f'{EXMS}.validate_pricing') - - exchange = ExchangeResolver.load_exchange('zaif', default_conf) + default_conf['exchange']['name'] = 'zaif' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() - exchange = ExchangeResolver.load_exchange('Bittrex', default_conf) + default_conf['exchange']['name'] = 'Bittrex' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Bittrex) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() - exchange = ExchangeResolver.load_exchange('kraken', default_conf) + default_conf['exchange']['name'] = 'kraken' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) assert not isinstance(exchange, Binance) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) - exchange = ExchangeResolver.load_exchange('binance', default_conf) + default_conf['exchange']['name'] = 'binance' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) assert not isinstance(exchange, Kraken) @@ -257,7 +260,8 @@ def test_exchange_resolver(default_conf, mocker, caplog): caplog) # Test mapping - exchange = ExchangeResolver.load_exchange('binanceus', default_conf) + default_conf['exchange']['name'] = 'binanceus' + exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) assert not isinstance(exchange, Kraken) @@ -990,19 +994,20 @@ def test_validate_pricing(default_conf, mocker): mocker.patch(f'{EXMS}.validate_timeframes') mocker.patch(f'{EXMS}.validate_stakecurrency') mocker.patch(f'{EXMS}.name', 'Binance') - ExchangeResolver.load_exchange('binance', default_conf) + default_conf['exchange']['name'] = 'binance' + ExchangeResolver.load_exchange(default_conf) has.update({'fetchTicker': False}) with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchTicker': True}) default_conf['exit_pricing']['use_order_book'] = True - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchL2OrderBook': False}) with pytest.raises(OperationalException, match="Orderbook not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) has.update({'fetchL2OrderBook': True}) @@ -1011,7 +1016,7 @@ def test_validate_pricing(default_conf, mocker): default_conf['margin_mode'] = MarginMode.ISOLATED with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): - ExchangeResolver.load_exchange('binance', default_conf) + ExchangeResolver.load_exchange(default_conf) def test_validate_ordertypes(default_conf, mocker): @@ -1091,12 +1096,13 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, 'stoploss_on_exchange': True, 'stoploss_price_type': stopadv, } + default_conf['exchange']['name'] = exchange_name if expected: - ExchangeResolver.load_exchange(exchange_name, default_conf) + ExchangeResolver.load_exchange(default_conf) else: with pytest.raises(OperationalException, match=r'On exchange stoploss price type is not supported for .*'): - ExchangeResolver.load_exchange(exchange_name, default_conf) + ExchangeResolver.load_exchange(default_conf) def test_validate_order_types_not_in_config(default_conf, mocker): @@ -1773,6 +1779,71 @@ def test_fetch_positions(default_conf, mocker, exchange_name): "fetch_positions", "fetch_positions") +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): + + api_mock = MagicMock() + api_mock.fetch_orders = MagicMock(return_value=[ + limit_order['buy'], + limit_order['sell'], + ]) + api_mock.fetch_open_orders = MagicMock(return_value=[limit_order['buy']]) + api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order['buy']]) + + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + start_time = datetime.now(timezone.utc) - timedelta(days=5) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + # Not available in dry-run + assert exchange.fetch_orders('mocked', start_time) == [] + assert api_mock.fetch_orders.call_count == 0 + default_conf['dry_run'] = False + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 0 + assert api_mock.fetch_closed_orders.call_count == 0 + assert len(res) == 2 + + res = exchange.fetch_orders('mocked', start_time) + + api_mock.fetch_orders.reset_mock() + + def has_resp(_, endpoint): + if endpoint == 'fetchOrders': + return False + if endpoint == 'fetchClosedOrders': + return True + if endpoint == 'fetchOpenOrders': + return True + + mocker.patch(f'{EXMS}.exchange_has', has_resp) + + # happy path without fetchOrders + res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 0 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "fetch_orders", "fetch_orders", retries=1, + pair='mocked', since=start_time) + + # Unhappy path - first fetch-orders call fails. + api_mock.fetch_orders = MagicMock(side_effect=ccxt.NotSupported()) + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + + res = exchange.fetch_orders('mocked', start_time) + + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + + def test_fetch_trading_fees(default_conf, mocker): api_mock = MagicMock() tick = { diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 95414a83f..95efaac52 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -34,16 +34,13 @@ def is_mac() -> bool: def can_run_model(model: str) -> None: - if (is_arm() or is_py11()) and "Catboost" in model: + if is_arm() and "Catboost" in model: pytest.skip("CatBoost is not supported on ARM.") is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model if is_pytorch_model and is_mac() and not is_arm(): pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.") - if is_pytorch_model: - pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.") - @pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [ ('LightGBMRegressor', True, False, True, True, False, 0), diff --git a/tests/freqai/test_models/ReinforcementLearner_test_3ac.py b/tests/freqai/test_models/ReinforcementLearner_test_3ac.py index c267c76a8..f77120c3c 100644 --- a/tests/freqai/test_models/ReinforcementLearner_test_3ac.py +++ b/tests/freqai/test_models/ReinforcementLearner_test_3ac.py @@ -18,6 +18,11 @@ class ReinforcementLearner_test_3ac(ReinforcementLearner): """ User can override any function in BaseRLEnv and gym.Env. Here the user sets a custom reward based on profit and trade duration. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: diff --git a/tests/freqai/test_models/ReinforcementLearner_test_4ac.py b/tests/freqai/test_models/ReinforcementLearner_test_4ac.py index 29e3e3b64..4fc2b0005 100644 --- a/tests/freqai/test_models/ReinforcementLearner_test_4ac.py +++ b/tests/freqai/test_models/ReinforcementLearner_test_4ac.py @@ -18,6 +18,11 @@ class ReinforcementLearner_test_4ac(ReinforcementLearner): """ User can override any function in BaseRLEnv and gym.Env. Here the user sets a custom reward based on profit and trade duration. + + Warning! + This is function is a showcase of functionality designed to show as many possible + environment control features as possible. It is also designed to run quickly + on small computers. This is a benchmark, it is *not* for live production. """ def calculate_reward(self, action: int) -> float: diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8123e4689..51fddbb88 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -740,6 +740,33 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short assert cancel_mock.call_count == 1 +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_short): + ftbot, client = botclient + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) + stoploss_mock = MagicMock() + cancel_mock = MagicMock() + ftbot.handle_onexchange_order = MagicMock() + mocker.patch.multiple( + EXMS, + markets=PropertyMock(return_value=markets), + fetch_ticker=ticker, + cancel_order=cancel_mock, + cancel_stoploss_order=stoploss_mock, + ) + + rc = client_get(client, f"{BASE_URI}/trades/10/reload") + assert_response(rc, 502) + assert 'Could not find trade with id 10.' in rc.json()['error'] + assert ftbot.handle_onexchange_order.call_count == 0 + + create_mock_trades(fee, is_short=is_short) + Trade.commit() + + rc = client_get(client, f"{BASE_URI}/trades/5/reload") + assert ftbot.handle_onexchange_order.call_count == 1 + + def test_api_logs(botclient): ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/logs") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index fff3f956e..02e829b64 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -143,8 +143,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], " "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['trades'], ['delete'], ['cancel_open_order', 'coo'], ['performance'], " - "['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " + "['reload_trade'], ['trades'], ['delete'], ['cancel_open_order', 'coo'], " + "['performance'], ['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['delete_locks', 'unlock'], " "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], " @@ -1763,6 +1763,25 @@ async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0] +@pytest.mark.parametrize('is_short', [True, False]) +async def test_telegram_reload_trade_from_exchange(mocker, update, default_conf, fee, is_short): + + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + context = MagicMock() + context.args = [] + + await telegram._reload_trade_from_exchange(update=update, context=context) + assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + create_mock_trades(fee, is_short=is_short) + + context.args = [5] + + await telegram._reload_trade_from_exchange(update=update, context=context) + assert "Status: `Reloaded from orders from exchange`" in msg_mock.call_args_list[0][0][0] + + @pytest.mark.parametrize('is_short', [True, False]) async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): diff --git a/tests/test_configuration.py b/tests/test_configuration.py index c445b989d..5b09abbd3 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1271,7 +1271,7 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf): configuration.get_config() -def test_pairlist_resolving_fallback(mocker): +def test_pairlist_resolving_fallback(mocker, tmpdir): mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock())) mocker.patch("freqtrade.configuration.configuration.load_file", @@ -1290,7 +1290,7 @@ def test_pairlist_resolving_fallback(mocker): assert config['pairs'] == ['ETH/BTC', 'XRP/BTC'] assert config['exchange']['name'] == 'binance' - assert config['datadir'] == Path.cwd() / "user_data/data/binance" + assert config['datadir'] == Path(tmpdir) / "user_data/data/binance" @pytest.mark.parametrize("setting", [ diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ea99061b8..8aa3f63d5 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5552,6 +5552,51 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert log_has(f"Error updating {order['id']}.", caplog) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_short, caplog): + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + mock_uts = mocker.spy(freqtrade, 'update_trade_state') + + entry_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + mock_fo = mocker.patch(f'{EXMS}.fetch_orders', return_value=[ + entry_order, + exit_order, + ]) + + order_id = entry_order['id'] + + trade = Trade( + open_order_id=order_id, + pair='ETH/USDT', + fee_open=0.001, + fee_close=0.001, + open_rate=entry_order['price'], + open_date=arrow.utcnow().datetime, + stake_amount=entry_order['cost'], + amount=entry_order['amount'], + exchange="binance", + is_short=is_short, + leverage=1, + ) + + trade.orders.append(Order.parse_from_ccxt_object( + entry_order, 'ADA/USDT', entry_side(is_short)) + ) + Trade.session.add(trade) + freqtrade.handle_onexchange_order(trade) + assert log_has_re(r"Found previously unknown order .*", caplog) + assert mock_uts.call_count == 1 + assert mock_fo.call_count == 1 + + trade = Trade.session.scalars(select(Trade)).first() + + assert len(trade.orders) == 2 + assert trade.is_open is False + assert trade.exit_reason == ExitType.SOLD_ON_EXCHANGE.value + + def test_get_valid_price(mocker, default_conf_usdt) -> None: patch_RPCManager(mocker) patch_exchange(mocker) diff --git a/tests/test_integration.py b/tests/test_integration.py index 9fb9fd8b3..2949f1ef2 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -75,8 +75,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, _notify_exit=MagicMock(), ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock) - wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) - mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000)) + wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update") + mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000) + mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True) freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 9f04ba20a..377caf59c 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -1,5 +1,4 @@ from copy import deepcopy -from pathlib import Path from unittest.mock import MagicMock import pandas as pd @@ -282,13 +281,13 @@ def test_generate_Plot_filename(): assert fn == "freqtrade-plot-UNITTEST_BTC-5m.html" -def test_generate_plot_file(mocker, caplog): +def test_generate_plot_file(mocker, caplog, user_dir): fig = generate_empty_figure() plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock()) store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html", - directory=Path("user_data/plot")) + directory=user_dir / "plot") - expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html")) + expected_fn = str(user_dir / "plot/freqtrade-plot-UNITTEST_BTC-5m.html") assert plot_mock.call_count == 1 assert plot_mock.call_args[0][0] == fig assert (plot_mock.call_args_list[0][1]['filename'] diff --git a/tests/test_strategy_updater.py b/tests/test_strategy_updater.py index 597d49fda..3b48c952c 100644 --- a/tests/test_strategy_updater.py +++ b/tests/test_strategy_updater.py @@ -16,18 +16,18 @@ if sys.version_info < (3, 9): pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True) -def test_strategy_updater_start(tmpdir, capsys) -> None: +def test_strategy_updater_start(user_dir, capsys) -> None: # Effective test without mocks. teststrats = Path(__file__).parent / 'strategy/strats' - tmpdirp = Path(tmpdir) / "strategies" - tmpdirp.mkdir() + tmpdirp = Path(user_dir) / "strategies" + tmpdirp.mkdir(parents=True, exist_ok=True) shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp) old_code = (teststrats / 'strategy_test_v2.py').read_text() args = [ "strategy-updater", "--userdir", - str(tmpdir), + str(user_dir), "--strategy-list", "StrategyTestV2" ] @@ -36,9 +36,9 @@ def test_strategy_updater_start(tmpdir, capsys) -> None: start_strategy_update(pargs) - assert Path(tmpdir / "strategies_orig_updater").exists() + assert Path(user_dir / "strategies_orig_updater").exists() # Backup file exists - assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists() + assert Path(user_dir / "strategies_orig_updater" / 'strategy_test_v2.py').exists() # updated file exists new_file = Path(tmpdirp / 'strategy_test_v2.py') assert new_file.exists() diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 7ccc8d0f5..09adf6e15 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -3,9 +3,11 @@ from copy import deepcopy from unittest.mock import MagicMock import pytest +from sqlalchemy import select from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import DependencyException +from freqtrade.persistence import Trade from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet @@ -364,3 +366,48 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee): free = freqtrade.wallets.get_free('BTC') used = freqtrade.wallets.get_used('BTC') assert free + used == total + + +def test_check_exit_amount(mocker, default_conf, fee): + freqtrade = get_patched_freqtradebot(mocker, default_conf) + update_mock = mocker.patch("freqtrade.wallets.Wallets.update") + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + + create_mock_trades(fee, is_short=None) + trade = Trade.session.scalars(select(Trade)).first() + assert trade.amount == 123 + + assert freqtrade.wallets.check_exit_amount(trade) is True + assert update_mock.call_count == 0 + assert total_mock.call_count == 1 + + update_mock.reset_mock() + # Reduce returned amount to below the trade amount - which should + # trigger a wallet update and return False, triggering "order refinding" + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100) + assert freqtrade.wallets.check_exit_amount(trade) is False + assert update_mock.call_count == 1 + assert total_mock.call_count == 2 + + +def test_check_exit_amount_futures(mocker, default_conf, fee): + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + freqtrade = get_patched_freqtradebot(mocker, default_conf) + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + + create_mock_trades(fee, is_short=None) + trade = Trade.session.scalars(select(Trade)).first() + trade.trading_mode = 'futures' + assert trade.amount == 123 + + assert freqtrade.wallets.check_exit_amount(trade) is True + assert total_mock.call_count == 0 + + update_mock = mocker.patch("freqtrade.wallets.Wallets.update") + trade.amount = 150 + # Reduce returned amount to below the trade amount - which should + # trigger a wallet update and return False, triggering "order refinding" + assert freqtrade.wallets.check_exit_amount(trade) is False + assert total_mock.call_count == 0 + assert update_mock.call_count == 1