diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfc8ac3b6..5c80bc141 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - +permissions: + repository-projects: read jobs: build_linux: @@ -24,7 +25,7 @@ jobs: strategy: matrix: os: [ ubuntu-20.04, ubuntu-22.04 ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 @@ -90,14 +91,14 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 6 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 - run: | - flake8 - - name: Sort imports (isort) run: | isort --check . + - name: Run Ruff + run: | + ruff check --format=github . + - name: Mypy run: | mypy freqtrade scripts tests @@ -115,7 +116,7 @@ jobs: strategy: matrix: os: [ macos-latest ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 @@ -186,14 +187,14 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 - run: | - flake8 - - name: Sort imports (isort) run: | isort --check . + - name: Run Ruff + run: | + ruff check --format=github . + - name: Mypy run: | mypy freqtrade scripts @@ -212,7 +213,7 @@ jobs: strategy: matrix: os: [ windows-latest ] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 @@ -248,9 +249,9 @@ jobs: freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all - - name: Flake8 + - name: Run Ruff run: | - flake8 + ruff check --format=github . - name: Mypy run: | @@ -321,7 +322,6 @@ jobs: build_linux_online: # Run pytest with "live" checks runs-on: ubuntu-22.04 - # permissions: steps: - uses: actions/checkout@v3 @@ -425,7 +425,7 @@ jobs: python setup.py sdist bdist_wheel - name: Publish to PyPI (Test) - uses: pypa/gh-action-pypi-publish@v1.6.4 + uses: pypa/gh-action-pypi-publish@v1.8.3 if: (github.event_name == 'release') with: user: __token__ @@ -433,7 +433,7 @@ jobs: repository_url: https://test.pypi.org/legacy/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.6.4 + uses: pypa/gh-action-pypi-publish@v1.8.3 if: (github.event_name == 'release') with: user: __token__ @@ -466,12 +466,13 @@ jobs: - name: Build and test and push docker images env: - IMAGE_NAME: freqtradeorg/freqtrade BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} run: | build_helpers/publish_docker_multi.sh deploy_arm: + permissions: + packages: write needs: [ deploy ] # Only run on 64bit machines runs-on: [self-hosted, linux, ARM64] @@ -494,8 +495,9 @@ jobs: - name: Build and test and push docker images env: - IMAGE_NAME: freqtradeorg/freqtrade BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} + GHCR_USERNAME: ${{ github.actor }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | build_helpers/publish_docker_arm64.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57ce81b8c..4784055a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,16 +8,17 @@ repos: # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.991" + rev: "v1.0.1" hooks: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==5.3.0.0 + - types-cachetools==5.3.0.4 - types-filelock==3.2.7 - - types-requests==2.28.11.13 - - types-tabulate==0.9.0.0 - - types-python-dateutil==2.8.19.6 + - types-requests==2.28.11.16 + - types-tabulate==0.9.0.1 + - types-python-dateutil==2.8.19.10 + - SQLAlchemy==2.0.7 # stages: [push] - repo: https://github.com/pycqa/isort @@ -27,6 +28,12 @@ repos: name: isort (python) # stages: [push] + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: 'v0.0.255' + hooks: + - id: ruff + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4e0bc024..040aae39c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,16 +45,17 @@ pytest tests/test_.py::test_ ### 2. Test if your code is PEP8 compliant -#### Run Flake8 +#### Run Ruff ```bash -flake8 freqtrade tests scripts +ruff . ``` -We receive a lot of code that fails the `flake8` checks. +We receive a lot of code that fails the `ruff` checks. To help with that, we encourage you to install the git pre-commit -hook that will warn you when you try to commit code that fails these checks. -Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using-hooks.html). +hook that will warn you when you try to commit code that fails these checks. + +you can manually run pre-commit with `pre-commit run -a`. ##### Additional styles applied diff --git a/build_helpers/install_ta-lib.sh b/build_helpers/install_ta-lib.sh index 079d578b4..005d9abca 100755 --- a/build_helpers/install_ta-lib.sh +++ b/build_helpers/install_ta-lib.sh @@ -8,8 +8,8 @@ if [ -n "$2" ] || [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then tar zxvf ta-lib-0.4.0-src.tar.gz cd ta-lib \ && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \ - && curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess \ - && curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \ + && curl 'https://raw.githubusercontent.com/gcc-mirror/gcc/master/config.guess' -o config.guess \ + && curl 'https://raw.githubusercontent.com/gcc-mirror/gcc/master/config.sub' -o config.sub \ && ./configure --prefix=${INSTALL_LOC}/ \ && make if [ $? -ne 0 ]; then diff --git a/build_helpers/pre_commit_update.py b/build_helpers/pre_commit_update.py index 8724d8ade..e6b47d100 100644 --- a/build_helpers/pre_commit_update.py +++ b/build_helpers/pre_commit_update.py @@ -8,12 +8,17 @@ import yaml pre_commit_file = Path('.pre-commit-config.yaml') require_dev = Path('requirements-dev.txt') +require = Path('requirements.txt') with require_dev.open('r') as rfile: requirements = rfile.readlines() +with require.open('r') as rfile: + requirements.extend(rfile.readlines()) + # Extract types only -type_reqs = [r.strip('\n') for r in requirements if r.startswith('types-')] +type_reqs = [r.strip('\n') for r in requirements if r.startswith( + 'types-') or r.startswith('SQLAlchemy')] with pre_commit_file.open('r') as file: f = yaml.load(file, Loader=yaml.FullLoader) diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh index f3cedff2e..a6ecdbee6 100755 --- a/build_helpers/publish_docker_arm64.sh +++ b/build_helpers/publish_docker_arm64.sh @@ -3,6 +3,10 @@ # Use BuildKit, otherwise building on ARM fails export DOCKER_BUILDKIT=1 +IMAGE_NAME=freqtradeorg/freqtrade +CACHE_IMAGE=freqtradeorg/freqtrade_cache +GHCR_IMAGE_NAME=ghcr.io/freqtrade/freqtrade + # Replace / with _ to create a valid tag TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g") TAG_PLOT=${TAG}_plot @@ -14,7 +18,6 @@ TAG_ARM=${TAG}_arm TAG_PLOT_ARM=${TAG_PLOT}_arm TAG_FREQAI_ARM=${TAG_FREQAI}_arm TAG_FREQAI_RL_ARM=${TAG_FREQAI_RL}_arm -CACHE_IMAGE=freqtradeorg/freqtrade_cache echo "Running for ${TAG}" @@ -38,13 +41,13 @@ if [ $? -ne 0 ]; then echo "failed building multiarch images" return 1 fi -# Tag image for upload and next build step -docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot . docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_ARM} -f docker/Dockerfile.freqai . docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_RL_ARM} -f docker/Dockerfile.freqai_rl . +# Tag image for upload and next build step +docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM docker tag freqtrade:$TAG_FREQAI_ARM ${CACHE_IMAGE}:$TAG_FREQAI_ARM docker tag freqtrade:$TAG_FREQAI_RL_ARM ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM @@ -59,7 +62,6 @@ fi docker images -# docker push ${IMAGE_NAME} docker push ${CACHE_IMAGE}:$TAG_PLOT_ARM docker push ${CACHE_IMAGE}:$TAG_FREQAI_ARM docker push ${CACHE_IMAGE}:$TAG_FREQAI_RL_ARM @@ -82,14 +84,30 @@ docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI} docker manifest create ${IMAGE_NAME}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL_ARM} docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI_RL} +# copy images to ghcr.io + +alias crane="docker run --rm -i -v $(pwd)/.crane:/home/nonroot/.docker/ gcr.io/go-containerregistry/crane" +mkdir .crane +chmod a+rwx .crane + +echo "${GHCR_TOKEN}" | crane auth login ghcr.io -u "${GHCR_USERNAME}" --password-stdin + +crane copy ${IMAGE_NAME}:${TAG_FREQAI_RL} ${GHCR_IMAGE_NAME}:${TAG_FREQAI_RL} +crane copy ${IMAGE_NAME}:${TAG_FREQAI} ${GHCR_IMAGE_NAME}:${TAG_FREQAI} +crane copy ${IMAGE_NAME}:${TAG_PLOT} ${GHCR_IMAGE_NAME}:${TAG_PLOT} +crane copy ${IMAGE_NAME}:${TAG} ${GHCR_IMAGE_NAME}:${TAG} + # Tag as latest for develop builds if [ "${TAG}" = "develop" ]; then echo 'Tagging image as latest' docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG} docker manifest push -p ${IMAGE_NAME}:latest + + crane copy ${IMAGE_NAME}:latest ${GHCR_IMAGE_NAME}:latest fi docker images +rm -rf .crane # Cleanup old images from arm64 node. docker image prune -a --force --filter "until=24h" diff --git a/build_helpers/publish_docker_multi.sh b/build_helpers/publish_docker_multi.sh index 3e5e61564..27fa06b95 100755 --- a/build_helpers/publish_docker_multi.sh +++ b/build_helpers/publish_docker_multi.sh @@ -2,6 +2,8 @@ # The below assumes a correctly setup docker buildx environment +IMAGE_NAME=freqtradeorg/freqtrade +CACHE_IMAGE=freqtradeorg/freqtrade_cache # Replace / with _ to create a valid tag TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g") TAG_PLOT=${TAG}_plot @@ -11,7 +13,6 @@ TAG_PI="${TAG}_pi" PI_PLATFORM="linux/arm/v7" echo "Running for ${TAG}" -CACHE_IMAGE=freqtradeorg/freqtrade_cache CACHE_TAG=${CACHE_IMAGE}:${TAG_PI}_cache # Add commit and commit_message to docker container diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 925fc7862..ef5e6900b 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -12,6 +12,9 @@ This page provides you some basic concepts on how Freqtrade works and operates. * **Indicators**: Technical indicators (SMA, EMA, RSI, ...). * **Limit order**: Limit orders which execute at the defined limit price or better. * **Market order**: Guaranteed to fill, may move price depending on the order size. +* **Current Profit**: Currently pending (unrealized) profit for this trade. This is mainly used throughout the bot and UI. +* **Realized Profit**: Already realized profit. Only relevant in combination with [partial exits](strategy-callbacks.md#adjust-trade-position) - which also explains the calculation logic for this. +* **Total Profit**: Combined realized and unrealized profit. The relative number (%) is calculated against the total investment in this trade. ## Fee handling @@ -57,10 +60,10 @@ This loop will be repeated again and again until the bot is stopped. * Load historic data for configured pairlist. * Calls `bot_start()` once. -* Calls `bot_loop_start()` once. * Calculate indicators (calls `populate_indicators()` once per pair). * Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair). * Loops per candle simulating entry and exit points. + * Calls `bot_loop_start()` strategy callback. * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks. * Calls `adjust_entry_price()` strategy callback for open entry orders. * Check for trade entry signals (`enter_long` / `enter_short` columns). diff --git a/docs/deprecated.md b/docs/deprecated.md index 3b5b28b81..6719ce56d 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -74,3 +74,8 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re * `webhooksell`, `webhookexit` -> `exit` * `webhooksellfill`, `webhookexitfill` -> `exit_fill` * `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel` + + +## Removal of `populate_any_indicators` + +version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details. diff --git a/docs/developer.md b/docs/developer.md index 0546c20e9..1bc75551f 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -24,7 +24,7 @@ This will spin up a local server (usually on port 8000) so you can see if everyt To configure a development environment, you can either use the provided [DevContainer](#devcontainer-setup), or use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`. -This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`. +This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`. Then install the git hook scripts by running `pre-commit install`, so your changes will be verified locally before committing. This avoids a lot of waiting for CI already, as some basic formatting checks are done locally on your machine. diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 275062a33..9822a895a 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -46,7 +46,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `outlier_protection_percentage` | Enable to prevent outlier detection methods from discarding too much data. If more than `outlier_protection_percentage` % of points are detected as outliers by the SVM or DBSCAN, FreqAI will log a warning message and ignore outlier detection, i.e., the original dataset will be kept intact. If the outlier protection is triggered, no predictions will be made based on the training dataset.
**Datatype:** Float.
Default: `30`. | `reverse_train_test_order` | Split the feature dataset (see below) and use the latest data split for training and test on historical split of the data. This allows the model to be trained up to the most recent data point, while avoiding overfitting. However, you should be careful to understand the unorthodox nature of this parameter before employing it.
**Datatype:** Boolean.
Default: `False` (no reversal). | `shuffle_after_split` | Split the data into train and test sets, and then shuffle both sets individually.
**Datatype:** Boolean.
Default: `False`. -| `buffer_train_data_candles` | Cut `buffer_train_data_candles` off the beginning and end of the training data *after* the indicators were populated. The main example use is when predicting maxima and minima, the argrelextrema function cannot know the maxima/minima at the edges of the timerange. To improve model accuracy, it is best to compute argrelextrema on the full timerange and then use this function to cut off the edges (buffer) by the kernel. In another case, if the targets are set to a shifted price movement, this buffer is unnecessary because the shifted candles at the end of the timerange will be NaN and FreqAI will automatically cut those off of the training dataset.
**Datatype:** Boolean.
Default: `False`. +| `buffer_train_data_candles` | Cut `buffer_train_data_candles` off the beginning and end of the training data *after* the indicators were populated. The main example use is when predicting maxima and minima, the argrelextrema function cannot know the maxima/minima at the edges of the timerange. To improve model accuracy, it is best to compute argrelextrema on the full timerange and then use this function to cut off the edges (buffer) by the kernel. In another case, if the targets are set to a shifted price movement, this buffer is unnecessary because the shifted candles at the end of the timerange will be NaN and FreqAI will automatically cut those off of the training dataset.
**Datatype:** Integer.
Default: `0`. ### Data split parameters @@ -84,6 +84,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `add_state_info` | Tell FreqAI to include state information in the feature set for training and inferencing. The current state variables include trade duration, current profit, trade position. This is only available in dry/live runs, and is automatically switched to false for backtesting.
**Datatype:** bool.
Default: `False`. | `net_arch` | Network architecture which is well described in [`stable_baselines3` doc](https://stable-baselines3.readthedocs.io/en/master/guide/custom_policy.html#examples). In summary: `[, dict(vf=[], pi=[])]`. By default this is set to `[128, 128]`, which defines 2 shared hidden layers with 128 units each. | `randomize_starting_position` | Randomize the starting point of each episode to avoid overfitting.
**Datatype:** bool.
Default: `False`. +| `drop_ohlc_from_features` | Do not include the normalized ohlc data in the feature set passed to the agent during training (ohlc will still be used for driving the environment in all cases)
**Datatype:** Boolean.
**Default:** `False` ### Additional parameters diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 3810aec4e..f5679a4ba 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -55,7 +55,7 @@ where `ReinforcementLearner` will use the templated `ReinforcementLearner` from dataframe["&-action"] = 0 ``` -Most of the function remains the same as for typical Regressors, however, the function above shows how the strategy must pass the raw price data to the agent so that it has access to raw OHLCV in the training environment: +Most of the function remains the same as for typical Regressors, however, the function below shows how the strategy must pass the raw price data to the agent so that it has access to raw OHLCV in the training environment: ```python def feature_engineering_standard(self, dataframe, **kwargs): @@ -176,9 +176,11 @@ As you begin to modify the strategy and the prediction model, you will quickly r factor = 100 + pair = self.pair.replace(':', '') + # you can use feature values from dataframe # Assumes the shifted RSI indicator has been generated in the strategy. - rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" + rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{pair}_" f"{self.config['timeframe']}"].iloc[self._current_tick] # reward agent for entering trades @@ -246,13 +248,13 @@ FreqAI also provides a built in episodic summary logger called `self.tensorboard """ def calculate_reward(self, action: int) -> float: if not self._is_valid(action): - self.tensorboard_log("is_valid") + self.tensorboard_log("invalid") return -2 ``` !!! Note - The `self.tensorboard_log()` function is designed for tracking incremented objects only i.e. events, actions inside the training environment. If the event of interest is a float, the float can be passed as the second argument e.g. `self.tensorboard_log("float_metric1", 0.23)` would add 0.23 to `float_metric`. In this case you can also disable incrementing using `inc=False` parameter. + The `self.tensorboard_log()` function is designed for tracking incremented objects only i.e. events, actions inside the training environment. If the event of interest is a float, the float can be passed as the second argument e.g. `self.tensorboard_log("float_metric1", 0.23)`. In this case the metric values are not incremented. ### Choosing a base environment diff --git a/docs/freqai-running.md b/docs/freqai-running.md index 1eaee1bf2..f3ccc546f 100644 --- a/docs/freqai-running.md +++ b/docs/freqai-running.md @@ -128,6 +128,9 @@ The FreqAI specific parameter `label_period_candles` defines the offset (number You can choose to adopt a continual learning scheme by setting `"continual_learning": true` in the config. By enabling `continual_learning`, after training an initial model from scratch, subsequent trainings will start from the final model state of the preceding training. This gives the new model a "memory" of the previous state. By default, this is set to `False` which means that all new models are trained from scratch, without input from previous models. +???+ 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). + ## 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 5c6b5b2ce..ef8efb840 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -71,6 +71,10 @@ 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. + ### Usage with docker If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. diff --git a/docs/includes/protections.md b/docs/includes/protections.md index e0ad8189f..12af081c0 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -149,7 +149,7 @@ The below example assumes a timeframe of 1 hour: * Locks each pair after selling for an additional 5 candles (`CooldownPeriod`), giving other pairs a chance to get filled. * Stops trading for 4 hours (`4 * 1h candles`) if the last 2 days (`48 * 1h candles`) had 20 trades, which caused a max-drawdown of more than 20%. (`MaxDrawdown`). * Stops trading if more than 4 stoploss occur for all pairs within a 1 day (`24 * 1h candles`) limit (`StoplossGuard`). -* Locks all pairs that had 4 Trades within the last 6 hours (`6 * 1h candles`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`). +* Locks all pairs that had 2 Trades within the last 6 hours (`6 * 1h candles`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`). * Locks all pairs for 2 candles that had a profit of below 0.01 (<1%) within the last 24h (`24 * 1h candles`), a minimum of 4 trades. ``` python diff --git a/docs/installation.md b/docs/installation.md index 1c0aed7ba..6e8488b9f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -290,10 +290,8 @@ cd freqtrade #### Freqtrade install: Conda Environment -Prepare conda-freqtrade environment, using file `environment.yml`, which exist in main freqtrade directory - ```bash -conda env create -n freqtrade-conda -f environment.yml +conda create --name freqtrade python=3.10 ``` !!! Note "Creating Conda Environment" @@ -302,12 +300,9 @@ conda env create -n freqtrade-conda -f environment.yml ```bash # choose your own packages conda env create -n [name of the environment] [python version] [packages] - - # point to file with packages - conda env create -n [name of the environment] -f [file] ``` -#### Enter/exit freqtrade-conda environment +#### Enter/exit freqtrade environment To check available environments, type @@ -319,7 +314,7 @@ Enter installed environment ```bash # enter conda environment -conda activate freqtrade-conda +conda activate freqtrade # exit conda environment - don't do it now conda deactivate @@ -329,6 +324,7 @@ Install last python dependencies with pip ```bash python3 -m pip install --upgrade pip +python3 -m pip install -r requirements.txt python3 -m pip install -e . ``` @@ -336,7 +332,7 @@ Patch conda libta-lib (Linux only) ```bash # Ensure that the environment is active! -conda activate freqtrade-conda +conda activate freqtrade cd build_helpers bash install_ta-lib.sh ${CONDA_PREFIX} nosudo @@ -355,8 +351,8 @@ conda env list # activate base environment conda activate -# activate freqtrade-conda environment -conda activate freqtrade-conda +# activate freqtrade environment +conda activate freqtrade #deactivate any conda environments conda deactivate diff --git a/docs/producer-consumer.md b/docs/producer-consumer.md index 88e34d0d6..c52279f26 100644 --- a/docs/producer-consumer.md +++ b/docs/producer-consumer.md @@ -42,14 +42,14 @@ Enable subscribing to an instance by adding the `external_message_consumer` sect | `producers` | **Required.** List of producers
**Datatype:** Array. | `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.
**Datatype:** string | `producers.host` | **Required.** The hostname or IP address from your producer.
**Datatype:** string -| `producers.port` | **Required.** The port matching the above host.
**Datatype:** string +| `producers.port` | **Required.** The port matching the above host.
*Defaults to `8080`.*
**Datatype:** Integer | `producers.secure` | **Optional.** Use ssl in websockets connection. Default False.
**Datatype:** string | `producers.ws_token` | **Required.** `ws_token` as configured on the producer.
**Datatype:** string | | **Optional settings** | `wait_timeout` | Timeout until we ping again if no message is received.
*Defaults to `300`.*
**Datatype:** Integer - in seconds. -| `wait_timeout` | Ping timeout
*Defaults to `10`.*
**Datatype:** Integer - in seconds. +| `ping_timeout` | Ping timeout
*Defaults to `10`.*
**Datatype:** Integer - in seconds. | `sleep_time` | Sleep time before retrying to connect.
*Defaults to `10`.*
**Datatype:** Integer - in seconds. -| `remove_entry_exit_signals` | Remove signal columns from the dataframe (set them to 0) on dataframe receipt.
*Defaults to `10`.*
**Datatype:** Integer - in seconds. +| `remove_entry_exit_signals` | Remove signal columns from the dataframe (set them to 0) on dataframe receipt.
*Defaults to `False`.*
**Datatype:** Boolean. | `message_size_limit` | Size limit per message
*Defaults to `8`.*
**Datatype:** Integer - Megabytes. Instead of (or as well as) calculating indicators in `populate_indicators()` the follower instance listens on the connection to a producer instance's messages (or multiple producer instances in advanced configurations) and requests the producer's most recently analyzed dataframes for each pair in the active whitelist. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index f5e671e88..7f4215aef 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.3.7 mkdocs==1.4.2 -mkdocs-material==9.0.13 +mkdocs-material==9.1.4 mdx_truly_sane_lists==1.3 -pymdown-extensions==9.9.2 +pymdown-extensions==9.10 jinja2==3.1.2 diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 81366c66e..329908527 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -51,7 +51,8 @@ During hyperopt, this runs only once at startup. ## Bot loop start -A simple callback which is called once at the start of every bot throttling iteration (roughly every 5 seconds, unless configured differently). +A simple callback which is called once at the start of every bot throttling iteration in dry/live mode (roughly every 5 +seconds, unless configured differently) or once per candle in backtest/hyperopt mode. This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc. ``` python @@ -61,11 +62,12 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - def bot_loop_start(self, **kwargs) -> None: + def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). Might be used to perform pair-independent tasks (e.g. gather some remote resource for comparison) + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ if self.config['runmode'].value in ('live', 'dry_run'): @@ -316,11 +318,11 @@ class AwesomeStrategy(IStrategy): # evaluate highest to lowest, so that highest possible stop is used if current_profit > 0.40: - return stoploss_from_open(0.25, current_profit, is_short=trade.is_short) + return stoploss_from_open(0.25, current_profit, is_short=trade.is_short, leverage=trade.leverage) elif current_profit > 0.25: - return stoploss_from_open(0.15, current_profit, is_short=trade.is_short) + return stoploss_from_open(0.15, current_profit, is_short=trade.is_short, leverage=trade.leverage) elif current_profit > 0.20: - return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage) # return maximum stoploss value, keeping current stoploss price unchanged return 1 diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 462f20402..8b6654c6c 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -881,7 +881,7 @@ All columns of the informative dataframe will be available on the returning data ### *stoploss_from_open()* -Stoploss values returned from `custom_stoploss` must specify a percentage relative to `current_rate`, but sometimes you may want to specify a stoploss relative to the open price instead. `stoploss_from_open()` is a helper function to calculate a stoploss value that can be returned from `custom_stoploss` which will be equivalent to the desired percentage above the open price. +Stoploss values returned from `custom_stoploss` must specify a percentage relative to `current_rate`, but sometimes you may want to specify a stoploss relative to the entry point instead. `stoploss_from_open()` is a helper function to calculate a stoploss value that can be returned from `custom_stoploss` which will be equivalent to the desired trade profit above the entry point. ??? Example "Returning a stoploss relative to the open price from the custom stoploss function" @@ -889,6 +889,8 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit, False)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. + This function will consider leverage - so at 10x leverage, the actual stoploss would be 0.7% above $100 (0.7% * 10x = 7%). + ``` python @@ -907,7 +909,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati # once the profit has risen above 10%, keep the stoploss at 7% above the open price if current_profit > 0.10: - return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage) return 1 @@ -954,12 +956,14 @@ In some situations it may be confusing to deal with stops relative to current ra ## Additional data (Wallets) -The strategy provides access to the `Wallets` object. This contains the current balances on the exchange. +The strategy provides access to the `wallets` object. This contains the current balances on the exchange. -!!! Note - Wallets is not available during backtesting / hyperopt. +!!! Note "Backtesting / Hyperopt" + Wallets behaves differently depending on the function it's called. + Within `populate_*()` methods, it'll return the full wallet as configured. + Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process. -Please always check if `Wallets` is available to avoid failures during backtesting. +Please always check if `wallets` is available to avoid failures during backtesting. ``` python if self.wallets: @@ -1036,11 +1040,10 @@ from datetime import timedelta, datetime, timezone # Within populate indicators (or populate_buy): if self.config['runmode'].value in ('live', 'dry_run'): - # fetch closed trades for the last 2 days - trades = Trade.get_trades([Trade.pair == metadata['pair'], - Trade.open_date > datetime.utcnow() - timedelta(days=2), - Trade.is_open.is_(False), - ]).all() + # fetch closed trades for the last 2 days + trades = Trade.get_trades_proxy( + pair=metadata['pair'], is_open=False, + open_date=datetime.now(timezone.utc) - timedelta(days=2)) # Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy sumprofit = sum(trade.close_profit for trade in trades) if sumprofit < 0: diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 4626944c5..dc0ab0976 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -152,7 +152,7 @@ You can create your own keyboard in `config.json`: !!! Note "Supported Commands" Only the following commands are allowed. Command arguments are not supported! - `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` + `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`, `/marketdir` ## Telegram commands @@ -179,6 +179,7 @@ official commands. You can ask at any moment for help with `/help`. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). +| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. If no direction is provided, the currently set direction will be displayed. | **Modify Trade states** | | `/forceexit | /fx ` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). @@ -242,7 +243,7 @@ Enter Tag is configurable via Strategy. > **Enter Tag:** Awesome Long Signal > **Open Rate:** `0.00007489` > **Current Rate:** `0.00007489` -> **Current Profit:** `12.95%` +> **Unrealized Profit:** `12.95%` > **Stoploss:** `0.00007389 (-0.02%)` ### /status table @@ -416,3 +417,27 @@ ARDR/ETH 0.366667 0.143059 -0.01 ### /version > **Version:** `0.14.3` + +### /marketdir + +If a market direction is provided the command updates the user managed variable that represents the current market direction. +This variable is not set to any valid market direction on bot startup and must be set by the user. The example below is for `/marketdir long`: + +``` +Successfully updated marketdirection from none to long. +``` + +If no market direction is provided the command outputs the currently set market directions. The example below is for `/marketdir`: + +``` +Currently set marketdirection: even +``` + +You can use the market direction in your strategy via `self.market_direction`. + +!!! Warning "Bot restarts" + Please note that the market direction is not persisted, and will be reset after a bot restart/reload. + +!!! Danger "Backtesting" + As this value/variable is intended to be changed manually in dry/live trading. + Strategies using `market_direction` will probably not produce reliable, reproducible results (changes to this variable will not be reflected for backtesting). Use at your own risk. diff --git a/docs/utils.md b/docs/utils.md index 87c7f6aa6..eb675442f 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -955,3 +955,47 @@ Print trades with id 2 and 3 as json ``` bash freqtrade show-trades --db-url sqlite:///tradesv3.sqlite --trade-ids 2 3 --print-json ``` + +### Strategy-Updater + +Updates listed strategies or all strategies within the strategies folder to be v3 compliant. +If the command runs without --strategy-list then all strategies inside the strategies folder will be converted. +Your original strategy will remain available in the `user_data/strategies_orig_updater/` directory. + +!!! Warning "Conversion results" + Strategy updater will work on a "best effort" approach. Please do your due diligence and verify the results of the conversion. + We also recommend to run a python formatter (e.g. `black`) to format results in a sane manner. + +``` +usage: freqtrade strategy-updater [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] + +options: + -h, --help show this help message and exit + --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] + Provide a space-separated list of strategies to + backtest. Please note that timeframe needs to be set + either in config or via command line. When using this + together with `--export trades`, the strategy-name is + injected into the filename (so `backtest-data.json` + becomes `backtest-data-SampleStrategy.json` + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE, --log-file FILE + Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. + -d PATH, --datadir PATH, --data-dir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +``` diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 1b0d9d724..43d6728ee 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -26,7 +26,7 @@ Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7 As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.25-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). -Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows. +Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9, 3.10 and 3.11) and for 64bit Windows. Other versions must be downloaded from the above link. ``` powershell diff --git a/environment.yml b/environment.yml index afe7a7ed7..e69de29bb 100644 --- a/environment.yml +++ b/environment.yml @@ -1,75 +0,0 @@ -name: freqtrade -channels: - - conda-forge -# - defaults -dependencies: -# 1/4 req main - - python>=3.8,<=3.10 - - numpy - - pandas - - pip - - - py-find-1st - - aiohttp - - SQLAlchemy - - python-telegram-bot<20.0.0 - - arrow - - cachetools - - requests - - urllib3 - - jsonschema - - TA-Lib - - tabulate - - jinja2 - - blosc - - sdnotify - - fastapi - - uvicorn - - pyjwt - - aiofiles - - psutil - - colorama - - questionary - - prompt-toolkit - - schedule - - python-dateutil - - joblib - - pyarrow - - - # ============================ - # 2/4 req dev - - - coveralls - - flake8 - - mypy - - pytest - - pytest-asyncio - - pytest-cov - - pytest-mock - - isort - - nbconvert - - # ============================ - # 3/4 req hyperopt - - - scipy - - scikit-learn<1.2.0 - - filelock - - scikit-optimize - - progressbar2 - # ============================ - # 4/4 req plot - - - plotly - - jupyter - - - pip: - - pycoingecko - # - py_find_1st - - tables - - pytest-random-order - - ccxt - - flake8-tidy-imports - - -e . - # - python-rapidjso diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f25bb2e52..f56328674 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2023.2' +__version__ = '2023.3' if 'dev' in __version__: from pathlib import Path diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 788657cc8..66a9c995b 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -22,5 +22,6 @@ from freqtrade.commands.optimize_commands import (start_backtesting, start_backt start_edge, start_hyperopt) from freqtrade.commands.pairlist_commands import start_test_pairlist from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit +from freqtrade.commands.strategy_utils_commands import start_strategy_update from freqtrade.commands.trade_commands import start_trading from freqtrade.commands.webserver_commands import start_webserver diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py index 20afa7ffd..e928ccad7 100644 --- a/freqtrade/commands/analyze_commands.py +++ b/freqtrade/commands/analyze_commands.py @@ -40,8 +40,8 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s if (not Path(signals_file).exists()): raise OperationalException( - (f"Cannot find latest backtest signals file: {signals_file}." - "Run backtesting with `--export signals`.") + f"Cannot find latest backtest signals file: {signals_file}." + "Run backtesting with `--export signals`." ) return config diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index b53a1022d..47aa37fdf 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -111,10 +111,13 @@ ARGS_ANALYZE_ENTRIES_EXITS = ["exportfilename", "analysis_groups", "enter_reason NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-freqaimodels", "list-data", "hyperopt-list", "hyperopt-show", "backtest-filter", - "plot-dataframe", "plot-profit", "show-trades", "trades-to-ohlcv"] + "plot-dataframe", "plot-profit", "show-trades", "trades-to-ohlcv", + "strategy-updater"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"] +ARGS_STRATEGY_UTILS = ["strategy_list", "strategy_path", "recursive_strategy_search"] + class Arguments: """ @@ -198,8 +201,8 @@ class Arguments: start_list_freqAI_models, start_list_markets, start_list_strategies, start_list_timeframes, start_new_config, start_new_strategy, start_plot_dataframe, - start_plot_profit, start_show_trades, start_test_pairlist, - start_trading, start_webserver) + start_plot_profit, start_show_trades, start_strategy_update, + start_test_pairlist, start_trading, start_webserver) subparsers = self.parser.add_subparsers(dest='command', # Use custom message when no subhandler is added @@ -440,3 +443,11 @@ class Arguments: parents=[_common_parser]) webserver_cmd.set_defaults(func=start_webserver) self._build_args(optionlist=ARGS_WEBSERVER, parser=webserver_cmd) + + # Add strategy_updater subcommand + strategy_updater_cmd = subparsers.add_parser('strategy-updater', + help='updates outdated strategy' + 'files to the current version', + parents=[_common_parser]) + strategy_updater_cmd.set_defaults(func=start_strategy_update) + self._build_args(optionlist=ARGS_STRATEGY_UTILS, parser=strategy_updater_cmd) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 2cd736b3e..bcef1c252 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from typing import Any, Dict, List from freqtrade.configuration import TimeRange, setup_utils_configuration -from freqtrade.constants import DATETIME_PRINT_FORMAT +from freqtrade.constants import DATETIME_PRINT_FORMAT, Config from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -20,15 +20,24 @@ from freqtrade.util.binance_mig import migrate_binance_futures_data logger = logging.getLogger(__name__) +def _data_download_sanity(config: Config) -> None: + if 'days' in config and 'timerange' in config: + raise OperationalException("--days and --timerange are mutually exclusive. " + "You can only specify one or the other.") + + if 'pairs' not in config: + raise OperationalException( + "Downloading data requires a list of pairs. " + "Please check the documentation on how to configure this.") + + def start_download_data(args: Dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) """ config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) - if 'days' in config and 'timerange' in config: - raise OperationalException("--days and --timerange are mutually exclusive. " - "You can only specify one or the other.") + _data_download_sanity(config) timerange = TimeRange() if 'days' in config: time_since = (datetime.now() - timedelta(days=config['days'])).strftime("%Y%m%d") @@ -40,11 +49,6 @@ def start_download_data(args: Dict[str, Any]) -> None: # Remove stake-currency to skip checks which are not relevant for datadownload config['stake_currency'] = '' - if 'pairs' not in config: - raise OperationalException( - "Downloading data requires a list of pairs. " - "Please check the documentation on how to configure this.") - pairs_not_available: List[str] = [] # Init exchange @@ -200,11 +204,14 @@ def start_list_data(args: Dict[str, Any]) -> None: pair, timeframe, candle_type, *dhc.ohlcv_data_min_max(pair, timeframe, candle_type) ) for pair, timeframe, candle_type in paircombs] + print(tabulate([ (pair, timeframe, candle_type, start.strftime(DATETIME_PRINT_FORMAT), end.strftime(DATETIME_PRINT_FORMAT)) - for pair, timeframe, candle_type, start, end in paircombs1 + for pair, timeframe, candle_type, start, end in sorted( + paircombs1, + key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2])) ], headers=("Pair", "Timeframe", "Type", 'From', 'To'), tablefmt='psql', stralign='right')) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index c424016b1..d83605c6f 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -1,7 +1,7 @@ import logging from typing import Any, Dict -from sqlalchemy import func +from sqlalchemy import func, select from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.enums import RunMode @@ -20,7 +20,7 @@ def start_convert_db(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) init_db(config['db_url']) - session_target = Trade._session + session_target = Trade.session init_db(config['db_url_from']) logger.info("Starting db migration.") @@ -36,16 +36,16 @@ def start_convert_db(args: Dict[str, Any]) -> None: session_target.commit() - for pairlock in PairLock.query: + for pairlock in PairLock.get_all_locks(): pairlock_count += 1 make_transient(pairlock) session_target.add(pairlock) session_target.commit() # Update sequences - max_trade_id = session_target.query(func.max(Trade.id)).scalar() - max_order_id = session_target.query(func.max(Order.id)).scalar() - max_pairlock_id = session_target.query(func.max(PairLock.id)).scalar() + max_trade_id = session_target.scalar(select(func.max(Trade.id))) + max_order_id = session_target.scalar(select(func.max(Order.id))) + max_pairlock_id = session_target.scalar(select(func.max(PairLock.id))) set_sequence_ids(session_target.get_bind(), trade_id=max_trade_id, diff --git a/freqtrade/commands/strategy_utils_commands.py b/freqtrade/commands/strategy_utils_commands.py new file mode 100644 index 000000000..e579ec475 --- /dev/null +++ b/freqtrade/commands/strategy_utils_commands.py @@ -0,0 +1,55 @@ +import logging +import sys +import time +from pathlib import Path +from typing import Any, Dict + +from freqtrade.configuration import setup_utils_configuration +from freqtrade.enums import RunMode +from freqtrade.resolvers import StrategyResolver +from freqtrade.strategy.strategyupdater import StrategyUpdater + + +logger = logging.getLogger(__name__) + + +def start_strategy_update(args: Dict[str, Any]) -> None: + """ + Start the strategy updating script + :param args: Cli args from Arguments() + :return: None + """ + + if sys.version_info == (3, 8): # pragma: no cover + sys.exit("Freqtrade strategy updater requires Python version >= 3.9") + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + strategy_objs = StrategyResolver.search_all_objects( + config, enum_failed=False, recursive=config.get('recursive_strategy_search', False)) + + filtered_strategy_objs = [] + if args['strategy_list']: + filtered_strategy_objs = [ + strategy_obj for strategy_obj in strategy_objs + if strategy_obj['name'] in args['strategy_list'] + ] + + else: + # Use all available entries. + filtered_strategy_objs = strategy_objs + + processed_locations = set() + for strategy_obj in filtered_strategy_objs: + if strategy_obj['location'] not in processed_locations: + processed_locations.add(strategy_obj['location']) + start_conversion(strategy_obj, config) + + +def start_conversion(strategy_obj, config): + print(f"Conversion of {Path(strategy_obj['location']).name} started.") + instance_strategy_updater = StrategyUpdater() + start = time.perf_counter() + instance_strategy_updater.start(config, strategy_obj) + elapsed = time.perf_counter() - start + print(f"Conversion of {Path(strategy_obj['location']).name} took {elapsed:.1f} seconds.") diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 606f081ef..0ee48cf91 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -27,10 +27,7 @@ def _extend_validator(validator_class): if 'default' in subschema: instance.setdefault(prop, subschema['default']) - for error in validate_properties( - validator, properties, instance, schema, - ): - yield error + yield from validate_properties(validator, properties, instance, schema) return validators.extend( validator_class, {'properties': set_defaults} diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index a1a77815a..57424468d 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -58,7 +58,7 @@ def load_config_file(path: str) -> Dict[str, Any]: """ try: # Read config from stdin if requested in the options - with open(path) if path != '-' else sys.stdin as file: + with Path(path).open() if path != '-' else sys.stdin as file: config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE) except FileNotFoundError: raise OperationalException( diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1727da92e..ebb946221 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -36,9 +36,10 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', ' 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] -AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] -AVAILABLE_DATAHANDLERS_TRADES = ['json', 'jsongz', 'hdf5'] -AVAILABLE_DATAHANDLERS = AVAILABLE_DATAHANDLERS_TRADES + ['feather', 'parquet'] +AVAILABLE_PROTECTIONS = ['CooldownPeriod', + 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] +AVAILABLE_DATAHANDLERS_TRADES = ['json', 'jsongz', 'hdf5', 'feather'] +AVAILABLE_DATAHANDLERS = AVAILABLE_DATAHANDLERS_TRADES + ['parquet'] BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] BACKTEST_CACHE_AGE = ['none', 'day', 'week', 'month'] BACKTEST_CACHE_DEFAULT = 'day' @@ -588,6 +589,7 @@ CONF_SCHEMA = { "rl_config": { "type": "object", "properties": { + "drop_ohlc_from_features": {"type": "boolean", "default": False}, "train_cycles": {"type": "integer"}, "max_trade_duration_candles": {"type": "integer"}, "add_state_info": {"type": "boolean", "default": False}, diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index c682436c7..3567f4112 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -346,7 +346,7 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str, return df_final[df_final['open_trades'] > max_open_trades] -def trade_list_to_dataframe(trades: List[LocalTrade]) -> pd.DataFrame: +def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.DataFrame: """ Convert list of Trade objects to pandas Dataframe :param trades: List of trade objects @@ -373,7 +373,7 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF filters = [] if strategy: filters.append(Trade.strategy == strategy) - trades = trade_list_to_dataframe(Trade.get_trades(filters).all()) + trades = trade_list_to_dataframe(list(Trade.get_trades(filters).all())) return trades diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 3991432a4..d05ee5db7 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -21,6 +21,7 @@ from freqtrade.exchange import Exchange, timeframe_to_seconds from freqtrade.exchange.types import OrderBook from freqtrade.misc import append_candles_to_dataframe from freqtrade.rpc import RPCManager +from freqtrade.rpc.rpc_types import RPCAnalyzedDFMsg from freqtrade.util import PeriodicCache @@ -118,8 +119,7 @@ class DataProvider: :param new_candle: This is a new candle """ if self.__rpc: - self.__rpc.send_msg( - { + msg: RPCAnalyzedDFMsg = { 'type': RPCMessageType.ANALYZED_DF, 'data': { 'key': pair_key, @@ -127,7 +127,7 @@ class DataProvider: 'la': datetime.now(timezone.utc) } } - ) + self.__rpc.send_msg(msg) if new_candle: self.__rpc.send_msg({ 'type': RPCMessageType.NEW_CANDLE, diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py index b2679bcea..5d67655cd 100644 --- a/freqtrade/data/entryexitanalysis.py +++ b/freqtrade/data/entryexitanalysis.py @@ -24,9 +24,9 @@ def _load_signal_candles(backtest_dir: Path): scpf = Path(backtest_dir.parent / f"{backtest_dir.stem}_signals.pkl") try: - scp = open(scpf, "rb") - signal_candles = joblib.load(scp) - logger.info(f"Loaded signal candles: {str(scpf)}") + with scpf.open("rb") as scp: + signal_candles = joblib.load(scp) + logger.info(f"Loaded signal candles: {str(scpf)}") except Exception as e: logger.error("Cannot load signal candles from pickled results: ", e) diff --git a/freqtrade/data/history/featherdatahandler.py b/freqtrade/data/history/featherdatahandler.py index 22a6805e7..bb387fc84 100644 --- a/freqtrade/data/history/featherdatahandler.py +++ b/freqtrade/data/history/featherdatahandler.py @@ -4,7 +4,7 @@ from typing import Optional from pandas import DataFrame, read_feather, to_datetime from freqtrade.configuration import TimeRange -from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, TradeList +from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList from freqtrade.enums import CandleType from .idatahandler import IDataHandler @@ -92,12 +92,11 @@ class FeatherDataHandler(IDataHandler): :param data: List of Lists containing trade data, column sequence as in DEFAULT_TRADES_COLUMNS """ - # filename = self._pair_trades_filename(self._datadir, pair) + filename = self._pair_trades_filename(self._datadir, pair) + self.create_dir_if_needed(filename) - raise NotImplementedError() - # array = pa.array(data) - # array - # feather.write_feather(data, filename) + tradesdata = DataFrame(data, columns=DEFAULT_TRADES_COLUMNS) + tradesdata.to_feather(filename, compression_level=9, compression='lz4') def trades_append(self, pair: str, data: TradeList): """ @@ -116,14 +115,13 @@ class FeatherDataHandler(IDataHandler): :param timerange: Timerange to load trades for - currently not implemented :return: List of trades """ - raise NotImplementedError() - # filename = self._pair_trades_filename(self._datadir, pair) - # tradesdata = misc.file_load_json(filename) + filename = self._pair_trades_filename(self._datadir, pair) + if not filename.exists(): + return [] - # if not tradesdata: - # return [] + tradesdata = read_feather(filename) - # return tradesdata + return tradesdata.values.tolist() @classmethod def _get_file_extension(cls): diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 8ef53e12d..69ef345e8 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -5,6 +5,7 @@ from freqtrade.enums.exitchecktuple import ExitCheckTuple from freqtrade.enums.exittype import ExitType from freqtrade.enums.hyperoptstate import HyperoptState from freqtrade.enums.marginmode import MarginMode +from freqtrade.enums.marketstatetype import MarketDirection from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.pricetype import PriceType from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType diff --git a/freqtrade/enums/candletype.py b/freqtrade/enums/candletype.py index 9d05ff6d7..dcb9f1448 100644 --- a/freqtrade/enums/candletype.py +++ b/freqtrade/enums/candletype.py @@ -13,6 +13,9 @@ class CandleType(str, Enum): FUNDING_RATE = "funding_rate" # BORROW_RATE = "borrow_rate" # * unimplemented + def __str__(self): + return f"{self.name.lower()}" + @staticmethod def from_string(value: str) -> 'CandleType': if not value: diff --git a/freqtrade/enums/marketstatetype.py b/freqtrade/enums/marketstatetype.py new file mode 100644 index 000000000..5cede32c2 --- /dev/null +++ b/freqtrade/enums/marketstatetype.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class MarketDirection(Enum): + """ + Enum for various market directions. + """ + LONG = "long" + SHORT = "short" + EVEN = "even" + NONE = "none" + + def __str__(self): + # convert to string + return self.value diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 2453d16d9..16d81b1d8 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -4,6 +4,7 @@ from enum import Enum class RPCMessageType(str, Enum): STATUS = 'status' WARNING = 'warning' + EXCEPTION = 'exception' STARTUP = 'startup' ENTRY = 'entry' @@ -37,5 +38,8 @@ class RPCRequestType(str, Enum): WHITELIST = 'whitelist' ANALYZED_DF = 'analyzed_df' + def __str__(self): + return self.value + NO_ECHO_MESSAGES = (RPCMessageType.ANALYZED_DF, RPCMessageType.WHITELIST, RPCMessageType.NEW_CANDLE) diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index f706fd4dc..b5af1f1b2 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -10,6 +10,9 @@ class SignalType(Enum): ENTER_SHORT = "enter_short" EXIT_SHORT = "exit_short" + def __str__(self): + return f"{self.name.lower()}" + class SignalTagType(Enum): """ @@ -18,7 +21,13 @@ class SignalTagType(Enum): ENTER_TAG = "enter_tag" EXIT_TAG = "exit_tag" + def __str__(self): + return f"{self.name.lower()}" + class SignalDirection(str, Enum): LONG = 'long' SHORT = 'short' + + def __str__(self): + return f"{self.name.lower()}" diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 740d6e8a0..7ac496f62 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,7 +23,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, "stoploss_order_types": {"limit": "stop_loss_limit"}, - "order_time_in_force": ['GTC', 'FOK', 'IOC'], + "order_time_in_force": ["GTC", "FOK", "IOC", "PO"], "ohlcv_candle_limit": 1000, "trades_pagination": "id", "trades_pagination_arg": "fromId", @@ -31,6 +31,7 @@ class Binance(Exchange): } _ft_has_futures: Dict = { "stoploss_order_types": {"limit": "stop", "market": "stop_market"}, + "order_time_in_force": ["GTC", "FOK", "IOC"], "tickers_have_price": False, "floor_leverage": True, "stop_price_type_field": "workingType", @@ -195,7 +196,7 @@ class Binance(Exchange): leverage_tiers_path = ( Path(__file__).parent / 'binance_leverage_tiers.json' ) - with open(leverage_tiers_path) as json_file: + with leverage_tiers_path.open() as json_file: return json_load(json_file) else: try: diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 22db74f06..597db27ff 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -104,10 +104,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", @@ -120,10 +120,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", @@ -134,13 +134,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -149,49 +149,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" } } ], @@ -658,10 +674,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", @@ -674,10 +690,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", @@ -688,18 +704,132 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "20700.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "45700.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": "295700.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": "1795700.0" + } + } + ], + "ACH/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": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, { "tier": 4.0, "currency": "USDT", @@ -713,7 +843,7 @@ "notionalCap": "250000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "5650.0" } }, { @@ -729,7 +859,7 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "11900.0" } }, { @@ -745,7 +875,7 @@ "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "386900.0" } } ], @@ -869,15 +999,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -893,7 +1023,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -909,7 +1039,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -925,77 +1055,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -1003,9 +1133,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -1114,10 +1244,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", @@ -1130,10 +1260,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "15", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -1144,13 +1274,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -1159,49 +1289,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": "5650.0" + "cum": "30650.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": "11900.0" + "cum": "70650.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": "320650.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": "386900.0" + "cum": "1820650.0" } } ], @@ -1701,14 +1847,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.012, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.012", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -1718,78 +1864,94 @@ "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", - "cum": "65.0" + "cum": "75.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "690.0" + "cum": "700.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5690.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11940.0" + "cum": "45700.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": "295700.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": "386940.0" + "cum": "1795700.0" } } ], @@ -2142,10 +2304,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", @@ -2158,10 +2320,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", @@ -2172,13 +2334,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": "650.0" @@ -2187,49 +2349,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": "5650.0" + "cum": "30650.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": "11900.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": "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": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" } } ], @@ -2353,14 +2531,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -2369,14 +2547,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, @@ -2384,112 +2562,96 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.0225, - "maxLeverage": 15.0, + "maxNotional": 900000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "15", - "notionalCap": "150000", + "initialLeverage": "20", + "notionalCap": "900000", "notionalFloor": "25000", - "maintMarginRatio": "0.0225", - "cum": "87.5" + "maintMarginRatio": "0.02", + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.025, + "minNotional": 900000.0, + "maxNotional": 1800000.0, + "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.025", - "cum": "462.5" + "notionalCap": "1800000", + "notionalFloor": "900000", + "maintMarginRatio": "0.05", + "cum": "27150.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "8", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.05", - "cum": "6712.5" + "initialLeverage": "5", + "notionalCap": "4800000", + "notionalFloor": "1800000", + "maintMarginRatio": "0.1", + "cum": "117150.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.1", - "cum": "56712.5" + "initialLeverage": "4", + "notionalCap": "6000000", + "notionalFloor": "4800000", + "maintMarginRatio": "0.125", + "cum": "237150.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 6000000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "106712.5" + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.25", + "cum": "987150.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "8", - "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.25", - "cum": "731712.5" - } - }, - { - "tier": 9.0, - "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "3231712.5" + "cum": "5487150.0" } } ], @@ -2591,6 +2753,136 @@ } } ], + "ARB/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.006", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.01", + "cum": "20.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "770.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "10770.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 800000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "50770.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "100770.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "725770.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "20000000", + "notionalFloor": "12000000", + "maintMarginRatio": "0.5", + "cum": "3725770.0" + } + } + ], "ARPA/USDT:USDT": [ { "tier": 1.0, @@ -2614,10 +2906,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -2628,13 +2920,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": "650.0" @@ -2643,49 +2935,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": "5650.0" + "cum": "30650.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": "11900.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": "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": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" } } ], @@ -2891,14 +3199,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -2915,61 +3223,61 @@ "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 200000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "200000", + "notionalCap": "600000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "767.5" + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 600000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1200000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "5767.5" + "cum": "15770.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1000000.0, + "minNotional": 1200000.0, + "maxNotional": 3200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "400000", + "notionalCap": "3200000", + "notionalFloor": "1200000", "maintMarginRatio": "0.1", - "cum": "25767.5" + "cum": "75770.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 3200000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -2977,41 +3285,41 @@ "bracket": "6", "initialLeverage": "4", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3200000", "maintMarginRatio": "0.125", - "cum": "50767.5" + "cum": "155770.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 5000000.0, - "maxNotional": 6000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "6000000", + "notionalCap": "12000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "675767.5" + "cum": "780770.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "2175767.5" + "cum": "3780770.0" } } ], @@ -3493,14 +3801,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -3509,14 +3817,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.0075, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.007, + "maxLeverage": 30.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "30", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.0075", + "maintMarginRatio": "0.007", "cum": "5.0" } }, @@ -3524,70 +3832,70 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 50000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 18.0, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "18", - "notionalCap": "50000", + "initialLeverage": "25", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.01", - "cum": "67.5" + "cum": "80.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "15", - "notionalCap": "250000", - "notionalFloor": "50000", + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "400000", "maintMarginRatio": "0.02", - "cum": "567.5" + "cum": "4080.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "1200000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "8067.5" + "cum": "22080.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1200000.0, + "maxNotional": 3200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3200000", + "notionalFloor": "1200000", "maintMarginRatio": "0.1", - "cum": "58067.5" + "cum": "82080.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, + "minNotional": 3200000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -3595,9 +3903,9 @@ "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalFloor": "3200000", "maintMarginRatio": "0.125", - "cum": "108067.5" + "cum": "162080.0" } }, { @@ -3605,15 +3913,15 @@ "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.1665, + "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", - "maintMarginRatio": "0.1665", - "cum": "315567.5" + "maintMarginRatio": "0.15", + "cum": "287080.0" } }, { @@ -3629,7 +3937,7 @@ "notionalCap": "15000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1150567.5" + "cum": "1287080.0" } }, { @@ -3645,7 +3953,7 @@ "notionalCap": "20000000", "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "4900567.5" + "cum": "5037080.0" } } ], @@ -4617,15 +4925,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -4641,7 +4949,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -4657,7 +4965,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -4673,77 +4981,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -4751,9 +5059,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -4762,96 +5070,96 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "8", - "notionalCap": "25000", + "initialLeverage": "20", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "6", - "notionalCap": "250000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "625.0" + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.1", - "cum": "13125.0" + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "4", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "25625.0" + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.25, + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "150625.0" + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 2000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1500000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "525625.0" + "cum": "386900.0" } } ], @@ -5054,13 +5362,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "1000000", + "notionalCap": "3000000", "notionalFloor": "250000", "maintMarginRatio": "0.01", "cum": "1300.0" @@ -5069,55 +5377,55 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 10000000.0, + "minNotional": 3000000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "10000000", - "notionalFloor": "1000000", + "notionalCap": "15000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.025", - "cum": "16300.0" + "cum": "46300.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.05", - "cum": "266300.0" + "cum": "421300.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 50000000.0, + "minNotional": 30000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalCap": "80000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.1", - "cum": "1266300.0" + "cum": "1921300.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 50000000.0, + "minNotional": 80000000.0, "maxNotional": 100000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -5125,9 +5433,9 @@ "bracket": "7", "initialLeverage": "4", "notionalCap": "100000000", - "notionalFloor": "50000000", + "notionalFloor": "80000000", "maintMarginRatio": "0.125", - "cum": "2516300.0" + "cum": "3921300.0" } }, { @@ -5143,7 +5451,7 @@ "notionalCap": "200000000", "notionalFloor": "100000000", "maintMarginRatio": "0.15", - "cum": "5016300.0" + "cum": "6421300.0" } }, { @@ -5159,7 +5467,7 @@ "notionalCap": "300000000", "notionalFloor": "200000000", "maintMarginRatio": "0.25", - "cum": "2.50163E7" + "cum": "2.64213E7" } }, { @@ -5175,7 +5483,7 @@ "notionalCap": "500000000", "notionalFloor": "300000000", "maintMarginRatio": "0.5", - "cum": "1.000163E8" + "cum": "1.014213E8" } } ], @@ -5293,6 +5601,120 @@ } } ], + "BTC/USDT:USDT-230630": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 375000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 375000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], "BTCDOM/USDT:USDT": [ { "tier": 1.0, @@ -5790,10 +6212,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", @@ -5806,10 +6228,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", @@ -5820,13 +6242,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": "650.0" @@ -5835,49 +6257,195 @@ { "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": "5650.0" + "cum": "30650.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": "11900.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": "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": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" + } + } + ], + "CFX/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": 25000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "25000", + "maintMarginRatio": "0.02", + "cum": "150.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 1200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1200000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "9150.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1200000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1200000", + "maintMarginRatio": "0.1", + "cum": "69150.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "144150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.25", + "cum": "894150.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 18000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "18000000", + "maintMarginRatio": "0.5", + "cum": "5394150.0" } } ], @@ -6077,6 +6645,218 @@ } } ], + "CKB/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" + } + } + ], + "COCOS/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": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "COMP/USDT:USDT": [ { "tier": 1.0, @@ -6492,10 +7272,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", @@ -6508,10 +7288,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", @@ -6522,13 +7302,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" @@ -6537,33 +7317,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" } }, { @@ -6571,15 +7351,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" } } ], @@ -6672,10 +7468,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", @@ -6688,10 +7484,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", @@ -6704,10 +7500,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", @@ -6734,13 +7530,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", + "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -6749,17 +7545,17 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "199400.0" } } ], @@ -6852,10 +7648,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", @@ -6868,10 +7664,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", @@ -6882,13 +7678,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": "650.0" @@ -6897,49 +7693,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": "5650.0" + "cum": "30650.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": "11900.0" + "cum": "70650.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": "320650.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": "386900.0" + "cum": "1820650.0" } } ], @@ -7146,10 +7958,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "15", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -7162,10 +7974,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", @@ -7176,13 +7988,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "200000", + "initialLeverage": "10", + "notionalCap": "300000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -7191,49 +8003,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 300000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "800000", + "notionalFloor": "300000", "maintMarginRatio": "0.1", - "cum": "10650.0" + "cum": "15650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.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": "500000", + "notionalFloor": "800000", "maintMarginRatio": "0.125", - "cum": "23150.0" + "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": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "398150.0" + "cum": "910650.0" } } ], @@ -7537,14 +8365,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -7553,14 +8381,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.0075, + "maintenanceMarginRate": 0.007, "maxLeverage": 40.0, "info": { "bracket": "2", "initialLeverage": "40", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.0075", + "maintMarginRatio": "0.007", "cum": "5.0" } }, @@ -7568,112 +8396,112 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 50000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "50000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.01", - "cum": "67.5" + "cum": "80.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, + "minNotional": 600000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "50000", + "notionalCap": "900000", + "notionalFloor": "600000", "maintMarginRatio": "0.025", - "cum": "817.5" + "cum": "9080.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "1800000", + "notionalFloor": "900000", "maintMarginRatio": "0.05", - "cum": "7067.5" + "cum": "31580.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "4800000", + "notionalFloor": "1800000", "maintMarginRatio": "0.1", - "cum": "57067.5" + "cum": "121580.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.125", - "cum": "107067.5" + "cum": "241580.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 6000000.0, + "maxNotional": 18000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "18000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.25", - "cum": "732067.5" + "cum": "991580.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "3232067.5" + "cum": "5491580.0" } } ], @@ -7944,10 +8772,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", @@ -7960,10 +8788,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", @@ -7974,13 +8802,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -7989,105 +8817,7 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], - "DYDX/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, - "info": { - "bracket": "1", - "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "250.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4000.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 200000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, @@ -8095,9 +8825,9 @@ "bracket": "4", "initialLeverage": "5", "notionalCap": "500000", - "notionalFloor": "250000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "16500.0" + "cum": "10650.0" } }, { @@ -8113,39 +8843,169 @@ "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29000.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 4000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "4000000", + "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "154000.0" + "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" + } + } + ], + "DYDX/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": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "300.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "10300.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 800000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "50300.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "100300.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.25", + "cum": "600300.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1154000.0" + "cum": "3600300.0" } } ], @@ -8186,13 +9046,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -8201,49 +9061,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": 50000000.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": "50000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1820700.0" } } ], @@ -8497,15 +9373,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -8521,7 +9397,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -8537,7 +9413,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -8553,77 +9429,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -8631,9 +9507,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -8757,15 +9633,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -8781,7 +9657,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -8797,7 +9673,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -8813,77 +9689,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -8891,9 +9767,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -9096,13 +9972,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "1000000", + "notionalCap": "2000000", "notionalFloor": "250000", "maintMarginRatio": "0.01", "cum": "1025.0" @@ -9111,71 +9987,71 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 2000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.02", - "cum": "11025.0" + "cum": "21025.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "25000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.05", - "cum": "161025.0" + "cum": "321025.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.1", - "cum": "661025.0" + "cum": "1571025.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, + "minNotional": 50000000.0, + "maxNotional": 60000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "notionalCap": "60000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.125", - "cum": "1161025.0" + "cum": "2821025.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 40000000.0, + "minNotional": 60000000.0, "maxNotional": 80000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, @@ -9183,9 +10059,9 @@ "bracket": "8", "initialLeverage": "3", "notionalCap": "80000000", - "notionalFloor": "40000000", + "notionalFloor": "60000000", "maintMarginRatio": "0.15", - "cum": "2161025.0" + "cum": "4321025.0" } }, { @@ -9201,7 +10077,7 @@ "notionalCap": "150000000", "notionalFloor": "80000000", "maintMarginRatio": "0.25", - "cum": "1.0161025E7" + "cum": "1.2321025E7" } }, { @@ -9217,7 +10093,7 @@ "notionalCap": "300000000", "notionalFloor": "150000000", "maintMarginRatio": "0.5", - "cum": "4.7661025E7" + "cum": "4.9821025E7" } } ], @@ -9335,6 +10211,120 @@ } } ], + "ETH/USDT:USDT-230630": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 375000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 375000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], "FET/USDT:USDT": [ { "tier": 1.0, @@ -9342,10 +10332,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", @@ -9358,10 +10348,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "15", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -9372,13 +10362,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -9387,49 +10377,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.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": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "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": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "898150.0" } } ], @@ -9537,14 +10543,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "50000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -9553,111 +10559,127 @@ "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", - "maintMarginRatio": "0.02", - "cum": "500.0" + "maintMarginRatio": "0.01", + "cum": "200.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "1000000", + "initialLeverage": "20", + "notionalCap": "600000", "notionalFloor": "250000", - "maintMarginRatio": "0.05", - "cum": "8000.0" + "maintMarginRatio": "0.02", + "cum": "2700.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 600000.0, + "maxNotional": 1200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.1", - "cum": "58000.0" + "initialLeverage": "10", + "notionalCap": "1200000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1200000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "108000.0" + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1200000", + "maintMarginRatio": "0.1", + "cum": "80700.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.1665, - "maxLeverage": 3.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.1665", - "cum": "315500.0" + "initialLeverage": "4", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "155700.0" } }, { "tier": 7.0, "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.165, + "maxLeverage": 3.0, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.165", + "cum": "395700.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1150500.0" + "cum": "1245700.0" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", "notionalCap": "30000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6150500.0" + "cum": "6245700.0" } } ], @@ -10075,14 +11097,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0075, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0075", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -10099,103 +11121,103 @@ "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", - "cum": "12.5" + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 150000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "150000", + "notionalCap": "400000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "762.5" + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "4512.5" + "cum": "10770.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, + "minNotional": 800000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "notionalCap": "2000000", + "notionalFloor": "800000", "maintMarginRatio": "0.1", - "cum": "17012.5" + "cum": "50770.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "5000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "29512.5" + "cum": "100770.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 4000000.0, + "minNotional": 5000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "1000000", + "notionalCap": "12000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "154512.5" + "cum": "725770.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1154512.5" + "cum": "3725770.0" } } ], @@ -10484,10 +11506,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", @@ -10500,10 +11522,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", @@ -10516,10 +11538,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", @@ -10546,13 +11568,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", + "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -10561,17 +11583,17 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "199400.0" } } ], @@ -10777,14 +11799,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -10801,103 +11823,103 @@ "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 200000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "200000", + "notionalCap": "900000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "767.5" + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1800000", + "notionalFloor": "900000", "maintMarginRatio": "0.05", - "cum": "5767.5" + "cum": "23270.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1000000.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "400000", + "notionalCap": "4800000", + "notionalFloor": "1800000", "maintMarginRatio": "0.1", - "cum": "25767.5" + "cum": "113270.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.125", - "cum": "50767.5" + "cum": "233270.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 6000000.0, + "minNotional": 6000000.0, + "maxNotional": 18000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "6000000", - "notionalFloor": "5000000", + "notionalCap": "18000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.25", - "cum": "675767.5" + "cum": "983270.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "2175767.5" + "cum": "5483270.0" } } ], @@ -11113,6 +12135,120 @@ } } ], + "GMX/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": 480000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "480000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 480000.0, + "maxNotional": 1280000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "1280000", + "notionalFloor": "480000", + "maintMarginRatio": "0.1", + "cum": "24650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1280000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1600000", + "notionalFloor": "1280000", + "maintMarginRatio": "0.125", + "cum": "56650.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 4800000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "4800000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.25", + "cum": "256650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 4800000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "8000000", + "notionalFloor": "4800000", + "maintMarginRatio": "0.5", + "cum": "1456650.0" + } + } + ], "GRT/USDT:USDT": [ { "tier": 1.0, @@ -11150,13 +12286,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -11165,49 +12301,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" } } ], @@ -11414,10 +12566,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", @@ -11430,10 +12582,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "15", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -11444,13 +12596,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -11459,49 +12611,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "20650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "45650.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": "295650.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": "386900.0" + "cum": "1795650.0" } } ], @@ -11512,10 +12680,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "15", + "initialLeverage": "10", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -11528,10 +12696,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", @@ -11542,13 +12710,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 120000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 6.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "120000", + "initialLeverage": "6", + "notionalCap": "300000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -11557,49 +12725,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 120000.0, - "maxNotional": 300000.0, + "minNotional": 300000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "300000", - "notionalFloor": "120000", + "notionalCap": "800000", + "notionalFloor": "300000", "maintMarginRatio": "0.1", - "cum": "6650.0" + "cum": "15650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.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": "300000", + "notionalFloor": "800000", "maintMarginRatio": "0.125", - "cum": "14150.0" + "cum": "35650.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "160650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "2000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "389150.0" + "cum": "535650.0" } } ], @@ -11799,104 +12983,6 @@ } } ], - "ICP/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "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": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "ICP/USDT:USDT": [ { "tier": 1.0, @@ -11904,10 +12990,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", @@ -11920,10 +13006,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", @@ -11934,13 +13020,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" @@ -11949,49 +13035,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": 30000000.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": "30000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1820700.0" } } ], @@ -12093,6 +13195,120 @@ } } ], + "ID/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, @@ -12100,10 +13316,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", @@ -12116,10 +13332,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", @@ -12130,13 +13346,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" @@ -12145,49 +13361,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" } } ], @@ -12198,10 +13430,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", @@ -12214,10 +13446,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", @@ -12228,13 +13460,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": "650.0" @@ -12243,49 +13475,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": "5650.0" + "cum": "30650.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": "11900.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": "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": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" } } ], @@ -12296,10 +13544,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", @@ -12312,10 +13560,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", @@ -12326,13 +13574,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -12341,33 +13589,33 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.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": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { @@ -12375,15 +13623,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": "148150.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": "898150.0" } } ], @@ -12800,80 +14064,112 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "625.0" + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5625.0" + "initialLeverage": "10", + "notionalCap": "400000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "4", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11875.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "45700.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": "295700.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386875.0" + "cum": "1795700.0" } } ], @@ -13177,14 +14473,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -13192,80 +14488,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "10770.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 800000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "50770.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2000000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "100770.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "725770.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "3725770.0" } } ], @@ -13374,10 +14702,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", @@ -13390,10 +14718,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", @@ -13404,13 +14732,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "900000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -13419,49 +14747,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 900000.0, + "maxNotional": 2400000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "2400000", + "notionalFloor": "900000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "45650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 2400000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "3000000", + "notionalFloor": "2400000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "105650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 3000000.0, + "maxNotional": 9000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "9000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.25", + "cum": "480650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 9000000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "15000000", + "notionalFloor": "9000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "2730650.0" } } ], @@ -13601,15 +14945,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -13625,7 +14969,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -13641,7 +14985,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -13657,77 +15001,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -13735,9 +15079,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -13921,6 +15265,120 @@ } } ], + "LQTY/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" + } + } + ], "LRC/USDT:USDT": [ { "tier": 1.0, @@ -14155,15 +15613,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -14179,7 +15637,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -14195,7 +15653,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -14211,77 +15669,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -14289,107 +15747,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" - } - } - ], - "LUNA2/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "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": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "9933045.0" } } ], @@ -14741,14 +16101,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -14757,79 +16117,111 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" + "maintMarginRatio": "0.02", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 900000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", + "initialLeverage": "20", + "notionalCap": "900000", "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" + "maintMarginRatio": "0.025", + "cum": "175.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" + "initialLeverage": "10", + "notionalCap": "1800000", + "notionalFloor": "900000", + "maintMarginRatio": "0.05", + "cum": "22675.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" + "initialLeverage": "5", + "notionalCap": "4800000", + "notionalFloor": "1800000", + "maintMarginRatio": "0.1", + "cum": "112675.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 8000000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "6000000", + "notionalFloor": "4800000", + "maintMarginRatio": "0.125", + "cum": "232675.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.25", + "cum": "982675.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "3000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "1136900.0" + "cum": "5482675.0" } } ], @@ -14953,14 +16345,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -14969,14 +16361,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.0075, + "maintenanceMarginRate": 0.007, "maxLeverage": 40.0, "info": { "bracket": "2", "initialLeverage": "40", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.0075", + "maintMarginRatio": "0.007", "cum": "5.0" } }, @@ -14984,214 +16376,116 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 50000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "50000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.01", - "cum": "67.5" + "cum": "80.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, + "minNotional": 600000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "notionalCap": "900000", + "notionalFloor": "600000", "maintMarginRatio": "0.025", - "cum": "817.5" + "cum": "9080.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "notionalCap": "1800000", + "notionalFloor": "900000", "maintMarginRatio": "0.05", - "cum": "4567.5" + "cum": "31580.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "notionalCap": "4800000", + "notionalFloor": "1800000", "maintMarginRatio": "0.1", - "cum": "17067.5" + "cum": "121580.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 750000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "500000", + "notionalCap": "6000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.125", - "cum": "29567.5" + "cum": "241580.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 3000000.0, + "minNotional": 6000000.0, + "maxNotional": 18000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "750000", + "notionalCap": "18000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.25", - "cum": "123317.5" + "cum": "991580.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 10000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "3000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "873317.5" + "cum": "5491580.0" } } ], "MINA/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": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], - "MKR/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", @@ -15228,13 +16522,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -15243,49 +16537,195 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" + } + } + ], + "MKR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.006", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.01", + "cum": "20.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 450000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "450000", + "notionalFloor": "25000", + "maintMarginRatio": "0.025", + "cum": "395.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 450000.0, + "maxNotional": 900000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "900000", + "notionalFloor": "450000", + "maintMarginRatio": "0.05", + "cum": "11645.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 900000.0, + "maxNotional": 2400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2400000", + "notionalFloor": "900000", + "maintMarginRatio": "0.1", + "cum": "56645.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2400000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "3000000", + "notionalFloor": "2400000", + "maintMarginRatio": "0.125", + "cum": "116645.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 9000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "9000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.25", + "cum": "491645.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 9000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "15000000", + "notionalFloor": "9000000", + "maintMarginRatio": "0.5", + "cum": "2741645.0" } } ], @@ -15296,10 +16736,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", @@ -15312,10 +16752,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", @@ -15326,13 +16766,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": "650.0" @@ -15341,49 +16781,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": "5650.0" + "cum": "30650.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": "11900.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": "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": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" } } ], @@ -15522,13 +16978,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 150000.0, - "maxNotional": 250000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", + "notionalCap": "600000", "notionalFloor": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" @@ -15537,65 +16993,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "notionalCap": "1600000", + "notionalFloor": "600000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "34500.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": "29500.0" + "cum": "74500.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "324500.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "1824500.0" } } ], @@ -15605,14 +17061,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -15620,80 +17076,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "10770.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 800000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "50770.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2000000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "100770.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "725770.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "3725770.0" } } ], @@ -16028,13 +17516,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 8.0, "info": { "bracket": "3", "initialLeverage": "8", - "notionalCap": "100000", + "notionalCap": "900000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -16043,49 +17531,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 900000.0, + "maxNotional": 2400000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "2400000", + "notionalFloor": "900000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "45650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 2400000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "3000000", + "notionalFloor": "2400000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "105650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 3000000.0, + "maxNotional": 9000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "9000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.25", + "cum": "480650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 9000000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "15000000", + "notionalFloor": "9000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "2730650.0" } } ], @@ -16224,13 +17728,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "600000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -16239,49 +17743,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" } } ], @@ -16291,14 +17811,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -16315,61 +17835,61 @@ "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 200000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "200000", + "notionalCap": "400000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "767.5" + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "5767.5" + "cum": "10770.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1000000.0, + "minNotional": 800000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "400000", + "notionalCap": "2000000", + "notionalFloor": "800000", "maintMarginRatio": "0.1", - "cum": "25767.5" + "cum": "50770.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -16377,41 +17897,41 @@ "bracket": "6", "initialLeverage": "4", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "50767.5" + "cum": "100770.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 5000000.0, - "maxNotional": 6000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "6000000", + "notionalCap": "12000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "675767.5" + "cum": "725770.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "2175767.5" + "cum": "3725770.0" } } ], @@ -16513,6 +18033,120 @@ } } ], + "PERP/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" + } + } + ], "PHB/BUSD:BUSD": [ { "tier": 1.0, @@ -16844,13 +18478,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -16859,49 +18493,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "10700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.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": "500000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "23200.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": "148200.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": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "898200.0" } } ], @@ -17108,10 +18758,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "15", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -17124,10 +18774,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", @@ -17138,13 +18788,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": "650.0" @@ -17153,49 +18803,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": "5650.0" + "cum": "30650.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": "11900.0" + "cum": "70650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.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": "320650.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": "1500000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1820650.0" } } ], @@ -17204,80 +18870,112 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.03, - "maxLeverage": 20.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.03", + "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "500.0" + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5500.0" + "initialLeverage": "10", + "notionalCap": "600000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "4", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11750.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "30650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "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": "5", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386750.0" + "cum": "1820650.0" } } ], @@ -17386,10 +19084,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", @@ -17402,10 +19100,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", @@ -17416,13 +19114,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -17431,49 +19129,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "10700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.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": "500000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "23200.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": "148200.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": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "898200.0" } } ], @@ -17484,10 +19198,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", @@ -17500,10 +19214,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", @@ -17514,13 +19228,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" @@ -17529,49 +19243,65 @@ { "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" } }, { "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": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "910650.0" } } ], @@ -17875,14 +19605,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -17891,14 +19621,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.0075, + "maintenanceMarginRate": 0.007, "maxLeverage": 40.0, "info": { "bracket": "2", "initialLeverage": "40", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.0075", + "maintMarginRatio": "0.007", "cum": "5.0" } }, @@ -17915,103 +19645,103 @@ "notionalCap": "50000", "notionalFloor": "25000", "maintMarginRatio": "0.01", - "cum": "67.5" + "cum": "80.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 150000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "150000", + "notionalCap": "400000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "817.5" + "cum": "830.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.05", - "cum": "4567.5" + "cum": "10830.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, + "minNotional": 800000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "notionalCap": "2000000", + "notionalFloor": "800000", "maintMarginRatio": "0.1", - "cum": "17067.5" + "cum": "50830.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "5000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "29567.5" + "cum": "100830.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "12000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "154567.5" + "cum": "725830.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1404567.5" + "cum": "3725830.0" } } ], @@ -18316,10 +20046,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", @@ -18332,10 +20062,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", @@ -18346,13 +20076,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -18361,49 +20091,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" } } ], @@ -18544,10 +20290,10 @@ "minNotional": 50000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "150000", "notionalFloor": "50000", "maintMarginRatio": "0.02", @@ -18558,13 +20304,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 150000.0, - "maxNotional": 250000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "15", - "notionalCap": "250000", + "initialLeverage": "20", + "notionalCap": "900000", "notionalFloor": "150000", "maintMarginRatio": "0.025", "cum": "1250.0" @@ -18573,81 +20319,81 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "1800000", + "notionalFloor": "900000", "maintMarginRatio": "0.05", - "cum": "7500.0" + "cum": "23750.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "4800000", + "notionalFloor": "1800000", "maintMarginRatio": "0.1", - "cum": "57500.0" + "cum": "113750.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.125", - "cum": "107500.0" + "cum": "233750.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 6000000.0, + "maxNotional": 18000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "18000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.25", - "cum": "732500.0" + "cum": "983750.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "3232500.0" + "cum": "5483750.0" } } ], @@ -18831,7 +20577,7 @@ } } ], - "STG/USDT:USDT": [ + "SSV/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", @@ -18854,10 +20600,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -18868,13 +20614,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18883,33 +20629,33 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.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": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { @@ -18917,15 +20663,145 @@ "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": "6", + "bracket": "7", "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "898150.0" + } + } + ], + "STG/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": 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": "386900.0" + "cum": "898150.0" } } ], @@ -18936,10 +20812,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", @@ -18952,10 +20828,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", @@ -18968,10 +20844,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", @@ -19014,13 +20890,13 @@ "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "1500000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -19064,13 +20940,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "300000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -19079,63 +20955,79 @@ { "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": "5700.0" + "cum": "15700.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": "11950.0" + "cum": "35700.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": "160700.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": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "910700.0" } } ], - "SUSHI/USDT:USDT": [ + "STX/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "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", @@ -19148,10 +21040,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", @@ -19162,13 +21054,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -19177,49 +21069,179 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" + } + } + ], + "SUSHI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "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": "75.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": "700.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": "30700.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": "70700.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": "320700.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": "1820700.0" } } ], @@ -19752,10 +21774,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", @@ -19768,10 +21790,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", @@ -19782,13 +21804,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 480000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "480000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -19797,49 +21819,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 480000.0, + "maxNotional": 1280000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1280000", + "notionalFloor": "480000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "24650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1280000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "1600000", + "notionalFloor": "1280000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "56650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 1600000.0, + "maxNotional": 4800000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "4800000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.25", + "cum": "256650.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 4800000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1456650.0" } } ], @@ -19941,6 +21979,120 @@ } } ], + "TRU/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" + } + } + ], "TRX/BUSD:BUSD": [ { "tier": 1.0, @@ -20403,14 +22555,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -20418,96 +22570,242 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "10000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", - "cum": "17.5" + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, + "minNotional": 50000.0, + "maxNotional": 900000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "10000", + "initialLeverage": "20", + "notionalCap": "900000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "167.5" + "cum": "770.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "minNotional": 900000.0, + "maxNotional": 1800000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", + "initialLeverage": "10", + "notionalCap": "1800000", + "notionalFloor": "900000", "maintMarginRatio": "0.05", - "cum": "792.5" + "cum": "23270.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 1800000.0, + "maxNotional": 4800000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "4800000", + "notionalFloor": "1800000", "maintMarginRatio": "0.1", - "cum": "5792.5" + "cum": "113270.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 5000000.0, + "minNotional": 4800000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "6000000", + "notionalFloor": "4800000", "maintMarginRatio": "0.125", - "cum": "12042.5" + "cum": "233270.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.25", + "cum": "983270.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 18000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "18000000", + "maintMarginRatio": "0.5", + "cum": "5483270.0" + } + } + ], + "USDC/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 30.0, + "info": { + "bracket": "1", + "initialLeverage": "30", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.006", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.01", + "cum": "20.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "770.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 1200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1200000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "15770.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 1200000.0, + "maxNotional": 3200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "3200000", + "notionalFloor": "1200000", + "maintMarginRatio": "0.1", + "cum": "75770.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 3200000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "3200000", + "maintMarginRatio": "0.125", + "cum": "155770.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 5000000.0, - "maxNotional": 8000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "780770.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1887042.5" + "cum": "3780770.0" } } ], @@ -20517,14 +22815,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.006", "cum": "0.0" } }, @@ -20533,63 +22831,63 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "25", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "20.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "20", + "notionalCap": "200000", "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "maintMarginRatio": "0.025", + "cum": "395.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5395.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "25395.0" } }, { @@ -20597,15 +22895,47 @@ "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "50395.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "675395.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "2175395.0" } } ], @@ -20678,13 +23008,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", + "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -20693,17 +23023,17 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "199400.0" } } ], @@ -20714,10 +23044,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "15", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -20730,10 +23060,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", @@ -20744,13 +23074,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 120000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "120000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -20759,33 +23089,33 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 120000.0, - "maxNotional": 300000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "300000", - "notionalFloor": "120000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "6650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", - "notionalFloor": "300000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "14150.0" + "cum": "23150.0" } }, { @@ -20793,15 +23123,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": "148150.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": "389150.0" + "cum": "898150.0" } } ], @@ -20910,10 +23256,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", @@ -20926,10 +23272,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", @@ -20940,13 +23286,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -20955,49 +23301,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "20650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "45650.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": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "295650.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": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1795650.0" } } ], @@ -21349,15 +23711,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, + "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.0065", - "cum": "7.5" + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { @@ -21373,7 +23735,7 @@ "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "42.5" + "cum": "45.0" } }, { @@ -21389,7 +23751,7 @@ "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "542.5" + "cum": "545.0" } }, { @@ -21405,77 +23767,77 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8042.5" + "cum": "8045.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58042.5" + "cum": "58045.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108042.5" + "cum": "183045.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.15", - "cum": "233042.5" + "cum": "433045.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "1233042.5" + "cum": "2433045.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 20000000.0, + "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, @@ -21483,9 +23845,9 @@ "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", - "notionalFloor": "20000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "6233042.5" + "cum": "9933045.0" } } ], @@ -21640,13 +24002,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "400000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "700.0" @@ -21655,49 +24017,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "5700.0" + "cum": "20700.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.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": "1000000", "maintMarginRatio": "0.125", - "cum": "11950.0" + "cum": "45700.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": "295700.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": "1795700.0" } } ], diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index c565b891f..a4b070741 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -27,11 +27,10 @@ class Bybit(Exchange): """ _ft_has: Dict = { - "ohlcv_candle_limit": 1000, + "ohlcv_candle_limit": 200, "ohlcv_has_history": False, } _ft_has_futures: Dict = { - "ohlcv_candle_limit": 200, "ohlcv_has_history": True, "mark_ohlcv_timeframe": "4h", "funding_fee_timeframe": "8h", @@ -115,7 +114,7 @@ class Bybit(Exchange): data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] return data - def _lev_prep(self, pair: str, leverage: float, side: BuySell): + def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): if self.trading_mode != TradingMode.SPOT: params = {'leverage': leverage} self.set_margin_mode(pair, self.margin_mode, accept_fail=True, params=params) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0cac411c7..bbe9585ae 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -60,7 +60,6 @@ class Exchange: _ft_has_default: Dict = { "stoploss_on_exchange": False, "order_time_in_force": ["GTC"], - "time_in_force_parameter": "timeInForce", "ohlcv_params": {}, "ohlcv_candle_limit": 500, "ohlcv_has_history": True, # Some exchanges (Kraken) don't provide history via ohlcv @@ -69,6 +68,7 @@ class Exchange: # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency "ohlcv_volume_currency": "base", # "base" or "quote" "tickers_have_quoteVolume": True, + "tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers "tickers_have_price": True, "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", @@ -80,6 +80,8 @@ class Exchange: "fee_cost_in_contracts": False, # Fee cost needs contract conversion "needs_trading_fees": False, # use fetch_trading_fees to cache fees "order_props_in_contracts": ['amount', 'cost', 'filled', 'remaining'], + # Override createMarketBuyOrderRequiresPrice where ccxt has it wrong + "marketOrderRequiresPrice": False, } _ft_has: Dict = {} _ft_has_futures: Dict = {} @@ -205,6 +207,8 @@ class Exchange: and self._api_async.session): logger.debug("Closing async ccxt session.") self.loop.run_until_complete(self._api_async.close()) + if self.loop and not self.loop.is_closed(): + self.loop.close() def validate_config(self, config): # Check if timeframe is available @@ -1018,10 +1022,10 @@ class Exchange: # Order handling - def _lev_prep(self, pair: str, leverage: float, side: BuySell): + def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): if self.trading_mode != TradingMode.SPOT: - self.set_margin_mode(pair, self.margin_mode) - self._set_leverage(leverage, pair) + self.set_margin_mode(pair, self.margin_mode, accept_fail) + self._set_leverage(leverage, pair, accept_fail) def _get_params( self, @@ -1033,12 +1037,18 @@ class Exchange: ) -> Dict: params = self._params.copy() if time_in_force != 'GTC' and ordertype != 'market': - param = self._ft_has.get('time_in_force_parameter', '') - params.update({param: time_in_force.upper()}) + params.update({'timeInForce': time_in_force.upper()}) if reduceOnly: params.update({'reduceOnly': True}) return params + def _order_needs_price(self, ordertype: str) -> bool: + return ( + ordertype != 'market' + or self._api.options.get("createMarketBuyOrderRequiresPrice", False) + or self._ft_has.get('marketOrderRequiresPrice', False) + ) + def create_order( self, *, @@ -1061,8 +1071,7 @@ class Exchange: try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) - needs_price = (ordertype != 'market' - or self._api.options.get("createMarketBuyOrderRequiresPrice", False)) + needs_price = self._order_needs_price(ordertype) rate_for_order = self.price_to_precision(pair, rate) if needs_price else None if not reduceOnly: @@ -1086,7 +1095,7 @@ class Exchange: f'Tried to {side} amount {amount} at rate {rate}.' f'Message: {e}') from e except ccxt.InvalidOrder as e: - raise ExchangeError( + raise InvalidOrderException( f'Could not create {ordertype} {side} order on market {pair}. ' f'Tried to {side} amount {amount} at rate {rate}. ' f'Message: {e}') from e @@ -1136,8 +1145,15 @@ class Exchange: "sell" else (stop_price >= limit_rate)) # Ensure rate is less than stop price if bad_stop_price: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') + # This can for example happen if the stop / liquidation price is set to 0 + # Which is possible if a market-order closes right away. + # The InvalidOrderException will bubble up to exit_positions, where it will be + # handled gracefully. + raise InvalidOrderException( + "In stoploss limit order, stop price should be more than limit price. " + f"Stop price: {stop_price}, Limit price: {limit_rate}, " + f"Limit Price pct: {limit_price_pct}" + ) return limit_rate def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: @@ -1200,7 +1216,7 @@ class Exchange: amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) - self._lev_prep(pair, leverage, side) + self._lev_prep(pair, leverage, side, accept_fail=True) order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, price=limit_rate, params=params) self._log_exchange_response('create_stoploss_order', order) @@ -1961,7 +1977,8 @@ class Exchange: cache: bool, drop_incomplete: bool) -> DataFrame: # keeping last candle time as last refreshed time of the pair if ticks and cache: - self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[-1][0] // 1000 + idx = -2 if drop_incomplete and len(ticks) > 1 else -1 + self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[idx][0] // 1000 # keeping parsed dataframe in cache ohlcv_df = ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=drop_incomplete) @@ -2034,7 +2051,9 @@ class Exchange: # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) plr = self._pairs_last_refresh_time.get((pair, timeframe, candle_type), 0) + interval_in_sec - return plr < arrow.utcnow().int_timestamp + # current,active candle open date + now = int(timeframe_to_prev_date(timeframe).timestamp()) + return plr < now @retrier_async async def _async_get_candle_history( @@ -2522,7 +2541,6 @@ class Exchange: self, leverage: float, pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None, accept_fail: bool = False, ): """ @@ -2540,7 +2558,7 @@ class Exchange: self._log_exchange_response('set_leverage', res) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e - except ccxt.BadRequest as e: + except (ccxt.BadRequest, ccxt.InsufficientFunds) as e: if not accept_fail: raise TemporaryError( f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e @@ -2751,10 +2769,10 @@ class Exchange: raise OperationalException( f"{self.name} does not support {self.margin_mode} {self.trading_mode}") - isolated_liq = None + liquidation_price = None if self._config['dry_run'] or not self.exchange_has("fetchPositions"): - isolated_liq = self.dry_run_liquidation_price( + liquidation_price = self.dry_run_liquidation_price( pair=pair, open_rate=open_rate, is_short=is_short, @@ -2769,16 +2787,16 @@ class Exchange: positions = self.fetch_positions(pair) if len(positions) > 0: pos = positions[0] - isolated_liq = pos['liquidationPrice'] + liquidation_price = pos['liquidationPrice'] - if isolated_liq is not None: - buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer - isolated_liq = ( - isolated_liq - buffer_amount + if liquidation_price is not None: + buffer_amount = abs(open_rate - liquidation_price) * self.liquidation_buffer + liquidation_price_buffer = ( + liquidation_price - buffer_amount if is_short else - isolated_liq + buffer_amount + liquidation_price + buffer_amount ) - return isolated_liq + return max(liquidation_price_buffer, 0.0) else: return None diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py index 80ed4088a..2ac135fc1 100644 --- a/freqtrade/exchange/gate.py +++ b/freqtrade/exchange/gate.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, Tuple from freqtrade.constants import BuySell from freqtrade.enums import MarginMode, PriceType, TradingMode -from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback2 @@ -28,10 +27,13 @@ class Gate(Exchange): "order_time_in_force": ['GTC', 'IOC'], "stoploss_order_types": {"limit": "limit"}, "stoploss_on_exchange": True, + "marketOrderRequiresPrice": True, } _ft_has_futures: Dict = { "needs_trading_fees": True, + "marketOrderRequiresPrice": False, + "tickers_have_bid_ask": False, "fee_cost_in_contracts": False, # Set explicitly to false for clarity "order_props_in_contracts": ['amount', 'filled', 'remaining'], "stop_price_type_field": "price_type", @@ -49,14 +51,6 @@ class Gate(Exchange): (TradingMode.FUTURES, MarginMode.ISOLATED) ] - def validate_ordertypes(self, order_types: Dict) -> None: - - if self.trading_mode != TradingMode.FUTURES: - if any(v == 'market' for k, v in order_types.items()): - raise OperationalException( - f'Exchange {self.name} does not support market orders.') - super().validate_stop_ordertypes(order_types) - def _get_params( self, side: BuySell, @@ -74,8 +68,7 @@ class Gate(Exchange): ) if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES: params['type'] = 'market' - param = self._ft_has.get('time_in_force_parameter', '') - params.update({param: 'IOC'}) + params.update({'timeInForce': 'IOC'}) return params def get_trades_for_order(self, order_id: str, pair: str, since: datetime, diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 8a4f7f7e0..b1a19fa69 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -158,7 +158,6 @@ class Kraken(Exchange): self, leverage: float, pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None, accept_fail: bool = False, ): """ diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 797d9fbd2..20e558513 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -64,6 +64,7 @@ class Kucoin(Exchange): # ccxt returns status = 'closed' at the moment - which is information ccxt invented. # Since we rely on status heavily, we must set it to 'open' here. # ref: https://github.com/ccxt/ccxt/pull/16674, (https://github.com/ccxt/ccxt/pull/16553) - res['type'] = ordertype - res['status'] = 'open' + if not self._config['dry_run']: + res['type'] = ordertype + res['status'] = 'open' return res diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index e7d658d24..a4fcaeca0 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,14 +1,16 @@ import logging -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import ccxt from freqtrade.constants import BuySell from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.enums.pricetype import PriceType -from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError +from freqtrade.exceptions import (DDosProtection, OperationalException, RetryableOrderError, + TemporaryError) from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange.common import retrier +from freqtrade.misc import safe_value_fallback2 logger = logging.getLogger(__name__) @@ -24,11 +26,13 @@ class Okx(Exchange): "ohlcv_candle_limit": 100, # Warning, special case with data prior to X months "mark_ohlcv_timeframe": "4h", "funding_fee_timeframe": "8h", + "stoploss_order_types": {"limit": "limit"}, + "stoploss_on_exchange": True, } _ft_has_futures: Dict = { "tickers_have_quoteVolume": False, "fee_cost_in_contracts": True, - "stop_price_type_field": "tpTriggerPxType", + "stop_price_type_field": "slTriggerPxType", "stop_price_type_value_mapping": { PriceType.LAST: "last", PriceType.MARK: "index", @@ -121,10 +125,9 @@ class Okx(Exchange): return params @retrier - def _lev_prep(self, pair: str, leverage: float, side: BuySell): + def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): if self.trading_mode != TradingMode.SPOT and self.margin_mode is not None: try: - # TODO-lev: Test me properly (check mgnMode passed) res = self._api.set_leverage( leverage=leverage, symbol=pair, @@ -157,3 +160,78 @@ class Okx(Exchange): pair_tiers = self._leverage_tiers[pair] return pair_tiers[-1]['maxNotional'] / leverage + + def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: + + params = self._params.copy() + # Verify if stopPrice works for your exchange! + params.update({'stopLossPrice': stop_price}) + + if self.trading_mode == TradingMode.FUTURES and self.margin_mode: + params['tdMode'] = self.margin_mode.value + params['posSide'] = self._get_posSide(side, True) + return params + + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: + """ + OKX uses non-default stoploss price naming. + """ + if not self._ft_has.get('stoploss_on_exchange'): + raise OperationalException(f"stoploss is not implemented for {self.name}.") + + return ( + order.get('stopLossPrice', None) is None + or ((side == "sell" and stop_loss > float(order['stopLossPrice'])) or + (side == "buy" and stop_loss < float(order['stopLossPrice']))) + ) + + def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: + if self._config['dry_run']: + return self.fetch_dry_run_order(order_id) + + try: + params1 = {'stop': True} + order_reg = self._api.fetch_order(order_id, pair, params=params1) + self._log_exchange_response('fetch_stoploss_order', order_reg) + return order_reg + except ccxt.OrderNotFound: + pass + params2 = {'stop': True, 'ordType': 'conditional'} + for method in (self._api.fetch_open_orders, self._api.fetch_closed_orders, + self._api.fetch_canceled_orders): + try: + orders = method(pair, params=params2) + orders_f = [order for order in orders if order['id'] == order_id] + if orders_f: + order = orders_f[0] + if (order['status'] == 'closed' + and (real_order_id := order.get('info', {}).get('ordId')) is not None): + # Once a order triggered, we fetch the regular followup order. + order_reg = self.fetch_order(real_order_id, pair) + self._log_exchange_response('fetch_stoploss_order1', order_reg) + order_reg['id_stop'] = order_reg['id'] + order_reg['id'] = order_id + order_reg['type'] = 'stoploss' + order_reg['status_stop'] = 'triggered' + return order_reg + order['type'] = 'stoploss' + return order + except ccxt.BaseError: + pass + raise RetryableOrderError( + f'StoplossOrder not found (pair: {pair} id: {order_id}).') + + def get_order_id_conditional(self, order: Dict[str, Any]) -> str: + if order['type'] == 'stop': + return safe_value_fallback2(order, order, 'id_stop', 'id') + return order['id'] + + def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: + params1 = {'stop': True} + # 'ordType': 'conditional' + # + return self.cancel_order( + order_id=order_id, + pair=pair, + params=params1, + ) diff --git a/freqtrade/freqai/RL/Base3ActionRLEnv.py b/freqtrade/freqai/RL/Base3ActionRLEnv.py index 3b5fffc58..a108d776e 100644 --- a/freqtrade/freqai/RL/Base3ActionRLEnv.py +++ b/freqtrade/freqai/RL/Base3ActionRLEnv.py @@ -47,7 +47,7 @@ class Base3ActionRLEnv(BaseEnvironment): self._update_unrealized_total_profit() step_reward = self.calculate_reward(action) self.total_reward += step_reward - self.tensorboard_log(self.actions._member_names_[action]) + self.tensorboard_log(self.actions._member_names_[action], category="actions") trade_type = None if self.is_tradesignal(action): diff --git a/freqtrade/freqai/RL/Base4ActionRLEnv.py b/freqtrade/freqai/RL/Base4ActionRLEnv.py index 8f45028b1..4f093f06c 100644 --- a/freqtrade/freqai/RL/Base4ActionRLEnv.py +++ b/freqtrade/freqai/RL/Base4ActionRLEnv.py @@ -48,7 +48,7 @@ class Base4ActionRLEnv(BaseEnvironment): self._update_unrealized_total_profit() step_reward = self.calculate_reward(action) self.total_reward += step_reward - self.tensorboard_log(self.actions._member_names_[action]) + self.tensorboard_log(self.actions._member_names_[action], category="actions") trade_type = None if self.is_tradesignal(action): diff --git a/freqtrade/freqai/RL/Base5ActionRLEnv.py b/freqtrade/freqai/RL/Base5ActionRLEnv.py index 22d3cae30..490ef3601 100644 --- a/freqtrade/freqai/RL/Base5ActionRLEnv.py +++ b/freqtrade/freqai/RL/Base5ActionRLEnv.py @@ -49,7 +49,7 @@ class Base5ActionRLEnv(BaseEnvironment): self._update_unrealized_total_profit() step_reward = self.calculate_reward(action) self.total_reward += step_reward - self.tensorboard_log(self.actions._member_names_[action]) + self.tensorboard_log(self.actions._member_names_[action], category="actions") trade_type = None if self.is_tradesignal(action): diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py index 7a4467bf7..7ac77361c 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -137,7 +137,8 @@ class BaseEnvironment(gym.Env): self.np_random, seed = seeding.np_random(seed) return [seed] - def tensorboard_log(self, metric: str, value: Union[int, float] = 1, inc: bool = True): + def tensorboard_log(self, metric: str, value: Optional[Union[int, float]] = None, + inc: Optional[bool] = None, category: str = "custom"): """ Function builds the tensorboard_metrics dictionary to be parsed by the TensorboardCallback. This @@ -149,17 +150,24 @@ class BaseEnvironment(gym.Env): def calculate_reward(self, action: int) -> float: if not self._is_valid(action): - self.tensorboard_log("is_valid") + self.tensorboard_log("invalid") return -2 :param metric: metric to be tracked and incremented - :param value: value to increment `metric` by - :param inc: sets whether the `value` is incremented or not + :param value: `metric` value + :param inc: (deprecated) sets whether the `value` is incremented or not + :param category: `metric` category """ - if not inc or metric not in self.tensorboard_metrics: - self.tensorboard_metrics[metric] = value + increment = True if value is None else False + value = 1 if increment else value + + if category not in self.tensorboard_metrics: + self.tensorboard_metrics[category] = {} + + if not increment or metric not in self.tensorboard_metrics[category]: + self.tensorboard_metrics[category][metric] = value else: - self.tensorboard_metrics[metric] += value + self.tensorboard_metrics[category][metric] += value def reset_tensorboard_log(self): self.tensorboard_metrics = {} diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index a8ef69394..e10880f46 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -114,6 +114,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): # normalize all data based on train_dataset only prices_train, prices_test = self.build_ohlc_price_dataframes(dk.data_dictionary, pair, dk) + data_dictionary = dk.normalize_data(data_dictionary) # data cleaning/analysis @@ -148,12 +149,8 @@ class BaseReinforcementLearningModel(IFreqaiModel): env_info = self.pack_env_dict(dk.pair) - self.train_env = self.MyRLEnv(df=train_df, - prices=prices_train, - **env_info) - self.eval_env = Monitor(self.MyRLEnv(df=test_df, - prices=prices_test, - **env_info)) + self.train_env = self.MyRLEnv(df=train_df, prices=prices_train, **env_info) + self.eval_env = Monitor(self.MyRLEnv(df=test_df, prices=prices_test, **env_info)) self.eval_callback = EvalCallback(self.eval_env, deterministic=True, render=False, eval_freq=len(train_df), best_model_save_path=str(dk.data_path)) @@ -238,6 +235,9 @@ class BaseReinforcementLearningModel(IFreqaiModel): filtered_dataframe, _ = dk.filter_features( unfiltered_df, dk.training_features_list, training_filter=False ) + + filtered_dataframe = self.drop_ohlc_from_df(filtered_dataframe, dk) + filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe) dk.data_dictionary["prediction_features"] = filtered_dataframe @@ -285,7 +285,6 @@ class BaseReinforcementLearningModel(IFreqaiModel): train_df = data_dictionary["train_features"] test_df = data_dictionary["test_features"] - # %-raw_volume_gen_shift-2_ETH/USDT_1h # price data for model training and evaluation tf = self.config['timeframe'] rename_dict = {'%-raw_open': 'open', '%-raw_low': 'low', @@ -318,8 +317,24 @@ class BaseReinforcementLearningModel(IFreqaiModel): prices_test.rename(columns=rename_dict, inplace=True) prices_test.reset_index(drop=True) + train_df = self.drop_ohlc_from_df(train_df, dk) + test_df = self.drop_ohlc_from_df(test_df, dk) + return prices_train, prices_test + def drop_ohlc_from_df(self, df: DataFrame, dk: FreqaiDataKitchen): + """ + Given a dataframe, drop the ohlc data + """ + drop_list = ['%-raw_open', '%-raw_low', '%-raw_high', '%-raw_close'] + + if self.rl_config["drop_ohlc_from_features"]: + df.drop(drop_list, axis=1, inplace=True) + feature_list = dk.training_features_list + dk.training_features_list = [e for e in feature_list if e not in drop_list] + + return df + def load_model_from_disk(self, dk: FreqaiDataKitchen) -> Any: """ Can be used by user if they are trying to limit_ram_usage *and* diff --git a/freqtrade/freqai/RL/TensorboardCallback.py b/freqtrade/freqai/RL/TensorboardCallback.py index b596742e9..7f8c76956 100644 --- a/freqtrade/freqai/RL/TensorboardCallback.py +++ b/freqtrade/freqai/RL/TensorboardCallback.py @@ -13,7 +13,7 @@ class TensorboardCallback(BaseCallback): episodic summary reports. """ def __init__(self, verbose=1, actions: Type[Enum] = BaseActions): - super(TensorboardCallback, self).__init__(verbose) + super().__init__(verbose) self.model: Any = None self.logger = None # type: Any self.training_env: BaseEnvironment = None # type: ignore @@ -46,14 +46,12 @@ class TensorboardCallback(BaseCallback): local_info = self.locals["infos"][0] tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0] - for info in local_info: - if info not in ["episode", "terminal_observation"]: - self.logger.record(f"_info/{info}", local_info[info]) + for metric in local_info: + if metric not in ["episode", "terminal_observation"]: + self.logger.record(f"info/{metric}", local_info[metric]) - for info in tensorboard_metrics: - if info in [action.name for action in self.actions]: - self.logger.record(f"_actions/{info}", tensorboard_metrics[info]) - else: - self.logger.record(f"_custom/{info}", tensorboard_metrics[info]) + for category in tensorboard_metrics: + for metric in tensorboard_metrics[category]: + self.logger.record(f"{category}/{metric}", tensorboard_metrics[category][metric]) return True diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index c90bb23fc..14986d854 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -126,7 +126,7 @@ class FreqaiDataDrawer: """ exists = self.global_metadata_path.is_file() if exists: - with open(self.global_metadata_path, "r") as fp: + with self.global_metadata_path.open("r") as fp: metatada_dict = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) return metatada_dict return {} @@ -139,7 +139,7 @@ class FreqaiDataDrawer: """ exists = self.pair_dictionary_path.is_file() if exists: - with open(self.pair_dictionary_path, "r") as fp: + with self.pair_dictionary_path.open("r") as fp: self.pair_dict = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) else: logger.info("Could not find existing datadrawer, starting from scratch") @@ -152,7 +152,7 @@ class FreqaiDataDrawer: if self.freqai_info.get('write_metrics_to_disk', False): exists = self.metric_tracker_path.is_file() if exists: - with open(self.metric_tracker_path, "r") as fp: + with self.metric_tracker_path.open("r") as fp: self.metric_tracker = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) logger.info("Loading existing metric tracker from disk.") else: @@ -166,7 +166,7 @@ class FreqaiDataDrawer: exists = self.historic_predictions_path.is_file() if exists: try: - with open(self.historic_predictions_path, "rb") as fp: + with self.historic_predictions_path.open("rb") as fp: self.historic_predictions = cloudpickle.load(fp) logger.info( f"Found existing historic predictions at {self.full_path}, but beware " @@ -176,7 +176,7 @@ class FreqaiDataDrawer: except EOFError: logger.warning( 'Historical prediction file was corrupted. Trying to load backup file.') - with open(self.historic_predictions_bkp_path, "rb") as fp: + with self.historic_predictions_bkp_path.open("rb") as fp: self.historic_predictions = cloudpickle.load(fp) logger.warning('FreqAI successfully loaded the backup historical predictions file.') @@ -189,7 +189,7 @@ class FreqaiDataDrawer: """ Save historic predictions pickle to disk """ - with open(self.historic_predictions_path, "wb") as fp: + with self.historic_predictions_path.open("wb") as fp: cloudpickle.dump(self.historic_predictions, fp, protocol=cloudpickle.DEFAULT_PROTOCOL) # create a backup @@ -200,16 +200,16 @@ class FreqaiDataDrawer: Save metric tracker of all pair metrics collected. """ with self.save_lock: - with open(self.metric_tracker_path, 'w') as fp: + with self.metric_tracker_path.open('w') as fp: rapidjson.dump(self.metric_tracker, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) - def save_drawer_to_disk(self): + def save_drawer_to_disk(self) -> None: """ Save data drawer full of all pair model metadata in present model folder. """ with self.save_lock: - with open(self.pair_dictionary_path, 'w') as fp: + with self.pair_dictionary_path.open('w') as fp: rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) @@ -218,7 +218,7 @@ class FreqaiDataDrawer: Save global metadata json to disk """ with self.save_lock: - with open(self.global_metadata_path, 'w') as fp: + with self.global_metadata_path.open('w') as fp: rapidjson.dump(metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) @@ -424,7 +424,7 @@ class FreqaiDataDrawer: dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns) dk.data["label_list"] = dk.label_list - with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: + with (save_path / f"{dk.model_filename}_metadata.json").open("w") as fp: rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) return @@ -457,7 +457,7 @@ class FreqaiDataDrawer: dk.data["training_features_list"] = dk.training_features_list dk.data["label_list"] = dk.label_list # store the metadata - with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: + with (save_path / f"{dk.model_filename}_metadata.json").open("w") as fp: rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) # save the train data to file so we can check preds for area of applicability later @@ -471,7 +471,7 @@ class FreqaiDataDrawer: if self.freqai_info["feature_parameters"].get("principal_component_analysis"): cloudpickle.dump( - dk.pca, open(dk.data_path / f"{dk.model_filename}_pca_object.pkl", "wb") + dk.pca, (dk.data_path / f"{dk.model_filename}_pca_object.pkl").open("wb") ) self.model_dictionary[coin] = model @@ -491,7 +491,7 @@ class FreqaiDataDrawer: Load only metadata into datakitchen to increase performance during presaved backtesting (prediction file loading). """ - with open(dk.data_path / f"{dk.model_filename}_metadata.json", "r") as fp: + with (dk.data_path / f"{dk.model_filename}_metadata.json").open("r") as fp: dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) dk.training_features_list = dk.data["training_features_list"] dk.label_list = dk.data["label_list"] @@ -514,7 +514,7 @@ class FreqaiDataDrawer: dk.data = self.meta_data_dictionary[coin]["meta_data"] dk.data_dictionary["train_features"] = self.meta_data_dictionary[coin]["train_df"] else: - with open(dk.data_path / f"{dk.model_filename}_metadata.json", "r") as fp: + with (dk.data_path / f"{dk.model_filename}_metadata.json").open("r") as fp: dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) dk.data_dictionary["train_features"] = pd.read_pickle( @@ -552,7 +552,7 @@ class FreqaiDataDrawer: if self.config["freqai"]["feature_parameters"]["principal_component_analysis"]: dk.pca = cloudpickle.load( - open(dk.data_path / f"{dk.model_filename}_pca_object.pkl", "rb") + (dk.data_path / f"{dk.model_filename}_pca_object.pkl").open("rb") ) return model @@ -570,12 +570,12 @@ class FreqaiDataDrawer: for pair in dk.all_pairs: for tf in feat_params.get("include_timeframes"): - + hist_df = history_data[pair][tf] # check if newest candle is already appended df_dp = strategy.dp.get_pair_dataframe(pair, tf) if len(df_dp.index) == 0: continue - if str(history_data[pair][tf].iloc[-1]["date"]) == str( + if str(hist_df.iloc[-1]["date"]) == str( df_dp.iloc[-1:]["date"].iloc[-1] ): continue @@ -583,21 +583,30 @@ class FreqaiDataDrawer: try: index = ( df_dp.loc[ - df_dp["date"] == history_data[pair][tf].iloc[-1]["date"] + df_dp["date"] == hist_df.iloc[-1]["date"] ].index[0] + 1 ) except IndexError: - logger.warning( - f"Unable to update pair history for {pair}. " - "If this does not resolve itself after 1 additional candle, " - "please report the error to #freqai discord channel" - ) - return + if hist_df.iloc[-1]['date'] < df_dp['date'].iloc[0]: + raise OperationalException("In memory historical data is older than " + f"oldest DataProvider candle for {pair} on " + f"timeframe {tf}") + else: + index = -1 + logger.warning( + f"No common dates in historical data and dataprovider for {pair}. " + f"Appending latest dataprovider candle to historical data " + "but please be aware that there is likely a gap in the historical " + "data. \n" + f"Historical data ends at {hist_df.iloc[-1]['date']} " + f"while dataprovider starts at {df_dp['date'].iloc[0]} and" + f"ends at {df_dp['date'].iloc[0]}." + ) history_data[pair][tf] = pd.concat( [ - history_data[pair][tf], + hist_df, df_dp.iloc[index:], ], ignore_index=True, diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 5d8e895a5..52d487b08 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -251,7 +251,7 @@ class FreqaiDataKitchen: (drop_index == 0) & (drop_index_labels == 0) ] logger.info( - f"dropped {len(unfiltered_df) - len(filtered_df)} training points" + f"{self.pair}: dropped {len(unfiltered_df) - len(filtered_df)} training points" f" due to NaNs in populated dataset {len(unfiltered_df)}." ) if (1 - len(filtered_df) / len(unfiltered_df)) > 0.1 and self.live: @@ -675,7 +675,7 @@ class FreqaiDataKitchen: ] logger.info( - f"SVM tossed {len(y_pred) - kept_points.sum()}" + f"{self.pair}: SVM tossed {len(y_pred) - kept_points.sum()}" f" test points from {len(y_pred)} total points." ) @@ -949,7 +949,7 @@ class FreqaiDataKitchen: if (len(do_predict) - do_predict.sum()) > 0: logger.info( - f"DI tossed {len(do_predict) - do_predict.sum()} predictions for " + f"{self.pair}: DI tossed {len(do_predict) - do_predict.sum()} predictions for " "being too far from training data." ) @@ -1315,123 +1315,54 @@ class FreqaiDataKitchen: dataframe: DataFrame = dataframe containing populated indicators """ - # this is a hack to check if the user is using the populate_any_indicators function + # check if the user is using the deprecated populate_any_indicators function new_version = inspect.getsource(strategy.populate_any_indicators) == ( inspect.getsource(IStrategy.populate_any_indicators)) - if new_version: - tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") - pairs: List[str] = self.freqai_config["feature_parameters"].get( - "include_corr_pairlist", []) + if not new_version: + raise OperationalException( + "You are using the `populate_any_indicators()` function" + " which was deprecated on March 1, 2023. Please refer " + "to the strategy migration guide to use the new " + "feature_engineering_* methods: \n" + "https://www.freqtrade.io/en/stable/strategy_migration/#freqai-strategy \n" + "And the feature_engineering_* documentation: \n" + "https://www.freqtrade.io/en/latest/freqai-feature-engineering/" + ) - for tf in tfs: - if tf not in base_dataframes: - base_dataframes[tf] = pd.DataFrame() - for p in pairs: - if p not in corr_dataframes: - corr_dataframes[p] = {} - if tf not in corr_dataframes[p]: - corr_dataframes[p][tf] = pd.DataFrame() - - if not prediction_dataframe.empty: - dataframe = prediction_dataframe.copy() - else: - dataframe = base_dataframes[self.config["timeframe"]].copy() - - corr_pairs: List[str] = self.freqai_config["feature_parameters"].get( - "include_corr_pairlist", []) - dataframe = self.populate_features(dataframe.copy(), pair, strategy, - corr_dataframes, base_dataframes) - metadata = {"pair": pair} - dataframe = strategy.feature_engineering_standard(dataframe.copy(), metadata=metadata) - # ensure corr pairs are always last - for corr_pair in corr_pairs: - if pair == corr_pair: - continue # dont repeat anything from whitelist - if corr_pairs and do_corr_pairs: - dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy, - corr_dataframes, base_dataframes, True) - - dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) - - self.get_unique_classes_from_labels(dataframe) - - dataframe = self.remove_special_chars_from_feature_names(dataframe) - - if self.config.get('reduce_df_footprint', False): - dataframe = reduce_dataframe_footprint(dataframe) - - return dataframe - - else: - # the user is using the populate_any_indicators functions which is deprecated - - df = self.use_strategy_to_populate_indicators_old_version( - strategy, corr_dataframes, base_dataframes, pair, - prediction_dataframe, do_corr_pairs) - return df - - def use_strategy_to_populate_indicators_old_version( - self, - strategy: IStrategy, - corr_dataframes: dict = {}, - base_dataframes: dict = {}, - pair: str = "", - prediction_dataframe: DataFrame = pd.DataFrame(), - do_corr_pairs: bool = True, - ) -> DataFrame: - """ - Use the user defined strategy for populating indicators during retrain - :param strategy: IStrategy = user defined strategy object - :param corr_dataframes: dict = dict containing the df pair dataframes - (for user defined timeframes) - :param base_dataframes: dict = dict containing the current pair dataframes - (for user defined timeframes) - :param metadata: dict = strategy furnished pair metadata - :return: - dataframe: DataFrame = dataframe containing populated indicators - """ - - # for prediction dataframe creation, we let dataprovider handle everything in the strategy - # so we create empty dictionaries, which allows us to pass None to - # `populate_any_indicators()`. Signaling we want the dp to give us the live dataframe. tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") - pairs: List[str] = self.freqai_config["feature_parameters"].get("include_corr_pairlist", []) + pairs: List[str] = self.freqai_config["feature_parameters"].get( + "include_corr_pairlist", []) + + for tf in tfs: + if tf not in base_dataframes: + base_dataframes[tf] = pd.DataFrame() + for p in pairs: + if p not in corr_dataframes: + corr_dataframes[p] = {} + if tf not in corr_dataframes[p]: + corr_dataframes[p][tf] = pd.DataFrame() + if not prediction_dataframe.empty: dataframe = prediction_dataframe.copy() - for tf in tfs: - base_dataframes[tf] = None - for p in pairs: - if p not in corr_dataframes: - corr_dataframes[p] = {} - corr_dataframes[p][tf] = None else: dataframe = base_dataframes[self.config["timeframe"]].copy() - sgi = False - for tf in tfs: - if tf == tfs[-1]: - sgi = True # doing this last allows user to use all tf raw prices in labels - dataframe = strategy.populate_any_indicators( - pair, - dataframe.copy(), - tf, - informative=base_dataframes[tf], - set_generalized_indicators=sgi - ) - + corr_pairs: List[str] = self.freqai_config["feature_parameters"].get( + "include_corr_pairlist", []) + dataframe = self.populate_features(dataframe.copy(), pair, strategy, + corr_dataframes, base_dataframes) + metadata = {"pair": pair} + dataframe = strategy.feature_engineering_standard(dataframe.copy(), metadata=metadata) # ensure corr pairs are always last - for corr_pair in pairs: + for corr_pair in corr_pairs: if pair == corr_pair: continue # dont repeat anything from whitelist - for tf in tfs: - if pairs and do_corr_pairs: - dataframe = strategy.populate_any_indicators( - corr_pair, - dataframe.copy(), - tf, - informative=corr_dataframes[corr_pair][tf] - ) + if corr_pairs and do_corr_pairs: + dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy, + corr_dataframes, base_dataframes, True) + + dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) self.get_unique_classes_from_labels(dataframe) diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 97f8c36b8..b657bd811 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -1,4 +1,3 @@ -import inspect import logging import threading import time @@ -105,8 +104,10 @@ class IFreqaiModel(ABC): self.data_provider: Optional[DataProvider] = None self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1) self.can_short = True # overridden in start() with strategy.can_short - - self.warned_deprecated_populate_any_indicators = False + self.model: Any = None + if self.ft_params.get('principal_component_analysis', False) and self.continual_learning: + self.ft_params.update({'principal_component_analysis': False}) + logger.warning('User tried to use PCA with continual learning. Deactivating PCA.') record_params(config, self.full_path) @@ -138,9 +139,6 @@ class IFreqaiModel(ABC): self.data_provider = strategy.dp self.can_short = strategy.can_short - # check if the strategy has deprecated populate_any_indicators function - self.check_deprecated_populate_any_indicators(strategy) - if self.live: self.inference_timer('start') self.dk = FreqaiDataKitchen(self.config, self.live, metadata["pair"]) @@ -159,8 +157,7 @@ class IFreqaiModel(ABC): dk = self.start_backtesting(dataframe, metadata, self.dk, strategy) dataframe = dk.remove_features_from_df(dk.return_dataframe) else: - logger.info( - "Backtesting using historic predictions (live models)") + logger.info("Backtesting using historic predictions (live models)") dk = self.start_backtesting_from_historic_predictions( dataframe, metadata, self.dk) dataframe = dk.return_dataframe @@ -344,13 +341,14 @@ class IFreqaiModel(ABC): except Exception as msg: logger.warning( f"Training {pair} raised exception {msg.__class__.__name__}. " - f"Message: {msg}, skipping.") + f"Message: {msg}, skipping.", exc_info=True) + self.model = None self.dd.pair_dict[pair]["trained_timestamp"] = int( tr_train.stopts) - if self.plot_features: + if self.plot_features and self.model is not None: plot_feature_importance(self.model, pair, dk, self.plot_features) - if self.save_backtest_models: + if self.save_backtest_models and self.model is not None: logger.info('Saving backtest model to disk.') self.dd.save_data(self.model, pair, dk) else: @@ -491,7 +489,7 @@ class IFreqaiModel(ABC): "strategy is furnishing the same features as the pretrained" "model. In case of --strategy-list, please be aware that FreqAI " "requires all strategies to maintain identical " - "populate_any_indicator() functions" + "feature_engineering_* functions" ) def data_cleaning_train(self, dk: FreqaiDataKitchen) -> None: @@ -603,7 +601,7 @@ class IFreqaiModel(ABC): :param strategy: IStrategy = user defined strategy object :param dk: FreqaiDataKitchen = non-persistent data container for current coin/loop :param data_load_timerange: TimeRange = the amount of data to be loaded - for populate_any_indicators + for populating indicators (larger than new_trained_timerange so that new_trained_timerange does not contain any NaNs) """ @@ -809,7 +807,7 @@ class IFreqaiModel(ABC): logger.warning("Couldn't cache corr_pair dataframes for improved performance. " "Consider ensuring that the full coin/stake, e.g. XYZ/USD, " "is included in the column names when you are creating features " - "in `populate_any_indicators()`.") + "in `feature_engineering_*` functions.") self.get_corr_dataframes = not bool(self.corr_dataframes) elif self.corr_dataframes: dataframe = dk.attach_corr_pair_columns( @@ -936,26 +934,6 @@ class IFreqaiModel(ABC): dk.return_dataframe, saved_dataframe, how='left', left_on='date', right_on="date_pred") return dk - def check_deprecated_populate_any_indicators(self, strategy: IStrategy): - """ - Check and warn if the deprecated populate_any_indicators function is used. - :param strategy: strategy object - """ - - if not self.warned_deprecated_populate_any_indicators: - self.warned_deprecated_populate_any_indicators = True - old_version = inspect.getsource(strategy.populate_any_indicators) != ( - inspect.getsource(IStrategy.populate_any_indicators)) - - if old_version: - logger.warning("DEPRECATION WARNING: " - "You are using the deprecated populate_any_indicators function. " - "This function will raise an error on March 1 2023. " - "Please update your strategy by using " - "the new feature_engineering functions. See \n" - "https://www.freqtrade.io/en/latest/freqai-feature-engineering/" - "for details.") - # Following methods which are overridden by user made prediction models. # See freqai/prediction_models/CatboostPredictionModel.py for an example. diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner.py b/freqtrade/freqai/prediction_models/ReinforcementLearner.py index 2a87151f9..e795703d4 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner.py @@ -100,7 +100,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel): """ # first, penalize if the action is not valid if not self._is_valid(action): - self.tensorboard_log("is_valid") + self.tensorboard_log("invalid", category="actions") return -2 pnl = self.get_unrealized_profit() diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py index 9ee035c95..b3b8c40e6 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py @@ -34,6 +34,11 @@ class ReinforcementLearner_multiproc(ReinforcementLearner): train_df = data_dictionary["train_features"] test_df = data_dictionary["test_features"] + if self.train_env: + self.train_env.close() + if self.eval_env: + self.eval_env.close() + env_info = self.pack_env_dict(dk.pair) env_id = "train_env" diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index 806e3ca15..2ba49ac40 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -211,7 +211,7 @@ def record_params(config: Dict[str, Any], full_path: Path) -> None: "pairs": config.get('exchange', {}).get('pair_whitelist') } - with open(params_record_path, "w") as handle: + with params_record_path.open("w") as handle: rapidjson.dump( run_params, handle, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 82be6f3b5..9746ac3d8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -30,6 +30,8 @@ from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager from freqtrade.rpc.external_message_consumer import ExternalMessageConsumer +from freqtrade.rpc.rpc_types import (RPCBuyMsg, RPCCancelMsg, RPCProtectionMsg, RPCSellCancelMsg, + RPCSellMsg) from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.util import FtPrecise @@ -127,19 +129,19 @@ class FreqtradeBot(LoggingMixin): for minutes in [0, 15, 30, 45]: t = str(time(time_slot, minutes, 2)) self._schedule.every().day.at(t).do(update) - self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.last_process: Optional[datetime] = None self.strategy.ft_bot_start() # Initialize protections AFTER bot start - otherwise parameters are not loaded. self.protections = ProtectionManager(self.config, self.strategy.protections) - def notify_status(self, msg: str) -> None: + def notify_status(self, msg: str, msg_type=RPCMessageType.STATUS) -> None: """ Public method for users of this class (worker, etc.) to send notifications via RPC about changes in the bot status. """ self.rpc.send_msg({ - 'type': RPCMessageType.STATUS, + 'type': msg_type, 'status': msg }) @@ -212,7 +214,8 @@ class FreqtradeBot(LoggingMixin): self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist), self.strategy.gather_informative_pairs()) - strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() + strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( + current_time=datetime.now(timezone.utc)) self.strategy.analyze(self.active_pair_whitelist) @@ -586,7 +589,7 @@ class FreqtradeBot(LoggingMixin): min_entry_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_entry_rate, - self.strategy.stoploss) + 0.0) min_exit_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_exit_rate, self.strategy.stoploss) @@ -594,7 +597,7 @@ class FreqtradeBot(LoggingMixin): stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None)( + default_retval=None, supress_error=True)( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_entry_rate, current_profit=current_entry_profit, min_stake=min_entry_stake, @@ -633,7 +636,7 @@ class FreqtradeBot(LoggingMixin): return remaining = (trade.amount - amount) * current_exit_rate - if remaining < min_exit_stake: + if min_exit_stake and remaining < min_exit_stake: logger.info(f"Remaining amount of {remaining} would be smaller " f"than the minimum of {min_exit_stake}.") return @@ -700,7 +703,8 @@ class FreqtradeBot(LoggingMixin): pos_adjust = trade is not None enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake( - pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_) + pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_, + pos_adjust) if not stake_amount: return False @@ -809,6 +813,9 @@ class FreqtradeBot(LoggingMixin): precision_mode=self.exchange.precisionMode, contract_size=self.exchange.get_contract_size(pair), ) + stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair) + trade.adjust_stop_loss(trade.open_rate, stoploss, initial=True) + else: # This is additional buy, we reset fee_open_currency so timeout checking can work trade.is_open = True @@ -818,7 +825,7 @@ class FreqtradeBot(LoggingMixin): trade.orders.append(order_obj) trade.recalc_trade_from_orders() - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() # Updating wallets @@ -841,7 +848,7 @@ class FreqtradeBot(LoggingMixin): def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade: # First cancelling stoploss on exchange ... - if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: + if trade.stoploss_order_id: try: logger.info(f"Canceling stoploss on exchange for {trade}") co = self.exchange.cancel_stoploss_order_with_result( @@ -850,7 +857,8 @@ class FreqtradeBot(LoggingMixin): # Reset stoploss order id. trade.stoploss_order_id = None except InvalidOrderException: - logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") + logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id} " + f"for pair {trade.pair}") return trade def get_valid_enter_price_and_stake( @@ -860,7 +868,12 @@ class FreqtradeBot(LoggingMixin): trade: Optional[Trade], order_adjust: bool, leverage_: Optional[float], + pos_adjust: bool, ) -> Tuple[float, float, float]: + """ + Validate and eventually adjust (within limits) limit, amount and leverage + :return: Tuple with (price, amount, leverage) + """ if price: enter_limit_requested = price @@ -906,7 +919,9 @@ class FreqtradeBot(LoggingMixin): # We do however also need min-stake to determine leverage, therefore this is ignored as # edge-case for now. min_stake_amount = self.exchange.get_min_pair_stake_amount( - pair, enter_limit_requested, self.strategy.stoploss, leverage) + pair, enter_limit_requested, + self.strategy.stoploss if not pos_adjust else 0.0, + leverage) max_stake_amount = self.exchange.get_max_pair_stake_amount( pair, enter_limit_requested, leverage) @@ -935,7 +950,6 @@ class FreqtradeBot(LoggingMixin): """ Sends rpc notification when a entry order occurred. """ - msg_type = RPCMessageType.ENTRY_FILL if fill else RPCMessageType.ENTRY open_rate = order.safe_price if open_rate is None: @@ -946,9 +960,9 @@ class FreqtradeBot(LoggingMixin): current_rate = self.exchange.get_rate( trade.pair, side='entry', is_short=trade.is_short, refresh=False) - msg = { + msg: RPCBuyMsg = { 'trade_id': trade.id, - 'type': msg_type, + 'type': RPCMessageType.ENTRY_FILL if fill else RPCMessageType.ENTRY, 'buy_tag': trade.enter_tag, 'enter_tag': trade.enter_tag, 'exchange': trade.exchange.capitalize(), @@ -960,6 +974,7 @@ class FreqtradeBot(LoggingMixin): 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], + 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency', None), 'amount': order.safe_amount_after_fee if fill else (order.amount or trade.amount), 'open_date': trade.open_date or datetime.utcnow(), @@ -978,7 +993,7 @@ class FreqtradeBot(LoggingMixin): current_rate = self.exchange.get_rate( trade.pair, side='entry', is_short=trade.is_short, refresh=False) - msg = { + msg: RPCCancelMsg = { 'trade_id': trade.id, 'type': RPCMessageType.ENTRY_CANCEL, 'buy_tag': trade.enter_tag, @@ -990,7 +1005,9 @@ class FreqtradeBot(LoggingMixin): 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, + 'open_rate': trade.open_rate, 'stake_currency': self.config['stake_currency'], + 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency', None), 'amount': trade.amount, 'open_date': trade.open_date, @@ -1013,12 +1030,16 @@ class FreqtradeBot(LoggingMixin): trades_closed = 0 for trade in trades: try: + try: + if (self.strategy.order_types.get('stoploss_on_exchange') and + self.handle_stoploss_on_exchange(trade)): + trades_closed += 1 + Trade.commit() + continue - if (self.strategy.order_types.get('stoploss_on_exchange') and - self.handle_stoploss_on_exchange(trade)): - trades_closed += 1 - Trade.commit() - continue + except InvalidOrderException as exception: + logger.warning( + f'Unable to handle stoploss on exchange for {trade.pair}: {exception}') # Check if we can sell our current pair if trade.open_order_id is None and trade.is_open and self.handle_trade(trade): trades_closed += 1 @@ -1122,8 +1143,7 @@ class FreqtradeBot(LoggingMixin): trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Exiting the trade forcefully') - self.execute_trade_exit(trade, stop_price, exit_check=ExitCheckTuple( - exit_type=ExitType.EMERGENCY_EXIT)) + self.emergency_exit(trade, stop_price) except ExchangeError: trade.stoploss_order_id = None @@ -1225,13 +1245,8 @@ class FreqtradeBot(LoggingMixin): # cancelling the current stoploss on exchange first logger.info(f"Cancelling current stoploss on exchange for pair {trade.pair} " f"(orderid:{order['id']}) in order to add another one ...") - try: - co = self.exchange.cancel_stoploss_order_with_result(order['id'], trade.pair, - trade.amount) - trade.update_order(co) - except InvalidOrderException: - logger.exception(f"Could not cancel stoploss order {order['id']} " - f"for pair {trade.pair}") + + self.cancel_stoploss_on_exchange(trade) # Create new stoploss order if not self.create_stoploss_order(trade=trade, stop_price=stoploss_norm): @@ -1275,20 +1290,22 @@ class FreqtradeBot(LoggingMixin): if order['side'] == trade.entry_side: self.handle_cancel_enter(trade, order, reason) else: - canceled = self.handle_cancel_exit( - trade, order, reason) + canceled = self.handle_cancel_exit(trade, order, reason) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: logger.warning(f'Emergency exiting trade {trade}, as the exit order ' f'timed out {max_timeouts} times.') - try: - self.execute_trade_exit( - trade, order['price'], - exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT)) - except DependencyException as exception: - logger.warning( - f'Unable to emergency sell trade {trade.pair}: {exception}') + self.emergency_exit(trade, order['price']) + + def emergency_exit(self, trade: Trade, price: float) -> None: + try: + self.execute_trade_exit( + trade, price, + exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT)) + except DependencyException as exception: + logger.warning( + f'Unable to emergency exit trade {trade.pair}: {exception}') def replace_order(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None: """ @@ -1315,7 +1332,7 @@ class FreqtradeBot(LoggingMixin): default_retval=order_obj.price)( trade=trade, order=order_obj, pair=trade.pair, current_time=datetime.now(timezone.utc), proposed_rate=proposed_rate, - current_order_rate=order_obj.price, entry_tag=trade.enter_tag, + current_order_rate=order_obj.safe_price, entry_tag=trade.enter_tag, side=trade.entry_side) replacing = True @@ -1331,7 +1348,8 @@ class FreqtradeBot(LoggingMixin): # place new order only if new price is supplied self.execute_entry( pair=trade.pair, - stake_amount=(order_obj.remaining * order_obj.price / trade.leverage), + stake_amount=( + order_obj.safe_remaining * order_obj.safe_price / trade.leverage), price=adjusted_entry_price, trade=trade, is_short=trade.is_short, @@ -1345,6 +1363,8 @@ class FreqtradeBot(LoggingMixin): """ for trade in Trade.get_open_order_trades(): + if not trade.open_order_id: + continue try: order = self.exchange.fetch_order(trade.open_order_id, trade.pair) except (ExchangeError): @@ -1369,6 +1389,9 @@ class FreqtradeBot(LoggingMixin): """ was_trade_fully_canceled = False side = trade.entry_side.capitalize() + if not trade.open_order_id: + logger.warning(f"No open order for {trade}.") + return False # Cancelled orders may have the status of 'canceled' or 'closed' if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: @@ -1455,34 +1478,32 @@ class FreqtradeBot(LoggingMixin): return False try: - co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, - trade.amount) + order = self.exchange.cancel_order_with_result(order['id'], trade.pair, + trade.amount) except InvalidOrderException: logger.exception( f"Could not cancel {trade.exit_side} order {trade.open_order_id}") return False - trade.close_rate = None - trade.close_rate_requested = None - trade.close_profit = None - trade.close_profit_abs = None + # Set exit_reason for fill message exit_reason_prev = trade.exit_reason trade.exit_reason = trade.exit_reason + f", {reason}" if trade.exit_reason else reason - self.update_trade_state(trade, trade.open_order_id, co) # Order might be filled above in odd timing issues. - if co.get('status') in ('canceled', 'cancelled'): + if order.get('status') in ('canceled', 'cancelled'): trade.exit_reason = None - trade.open_order_id = None else: trade.exit_reason = exit_reason_prev - - logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.') cancelled = True else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.') - self.update_trade_state(trade, trade.open_order_id, order) - trade.open_order_id = None + trade.exit_reason = None + + self.update_trade_state(trade, trade.open_order_id, order) + + logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.') + trade.open_order_id = None + trade.close_rate = None + trade.close_rate_requested = None self._notify_exit_cancel( trade, @@ -1640,13 +1661,13 @@ class FreqtradeBot(LoggingMixin): profit = trade.calc_profit(rate=order_rate, amount=amount, open_rate=trade.open_rate) profit_ratio = trade.calc_profit_ratio(order_rate, amount, trade.open_rate) else: - order_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + order_rate = trade.safe_close_rate profit = trade.calc_profit(rate=order_rate) + (0.0 if fill else trade.realized_profit) profit_ratio = trade.calc_profit_ratio(order_rate) amount = trade.amount gain = "profit" if profit_ratio > 0 else "loss" - msg = { + msg: RPCSellMsg = { 'type': (RPCMessageType.EXIT_FILL if fill else RPCMessageType.EXIT), 'trade_id': trade.id, @@ -1672,6 +1693,7 @@ class FreqtradeBot(LoggingMixin): 'close_date': trade.close_date or datetime.utcnow(), 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], + 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency'), 'sub_trade': sub_trade, 'cumulative_profit': trade.realized_profit, @@ -1695,14 +1717,14 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Order_obj not found for {order_id}. This should not have happened.") - profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + profit_rate: float = trade.safe_close_rate profit_trade = trade.calc_profit(rate=profit_rate) current_rate = self.exchange.get_rate( trade.pair, side='exit', is_short=trade.is_short, refresh=False) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" - msg = { + msg: RPCSellCancelMsg = { 'type': RPCMessageType.EXIT_CANCEL, 'trade_id': trade.id, 'exchange': trade.exchange.capitalize(), @@ -1724,6 +1746,7 @@ class FreqtradeBot(LoggingMixin): 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.now(timezone.utc), 'stake_currency': self.config['stake_currency'], + 'base_currency': self.exchange.get_pair_base_currency(trade.pair), 'fiat_currency': self.config.get('fiat_display_currency', None), 'reason': reason, 'sub_trade': sub_trade, @@ -1738,7 +1761,8 @@ class FreqtradeBot(LoggingMixin): # def update_trade_state( - self, trade: Trade, order_id: str, action_order: Optional[Dict[str, Any]] = None, + self, trade: Trade, order_id: Optional[str], + action_order: Optional[Dict[str, Any]] = None, stoploss_order: bool = False, send_msg: bool = True) -> bool: """ Checks trades with open orders and updates the amount if necessary @@ -1787,7 +1811,7 @@ class FreqtradeBot(LoggingMixin): # TODO: should shorting/leverage be supported by Edge, # then this will need to be fixed. trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) - if order.get('side') == trade.entry_side or trade.amount > 0: + if order.get('side') == trade.entry_side or (trade.amount > 0 and trade.is_open): # Must also run for partial exits # TODO: Margin will need to use interest_rate as well. # interest_rate = self.exchange.get_interest_rate() @@ -1830,14 +1854,20 @@ class FreqtradeBot(LoggingMixin): self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason='Auto lock') prot_trig = self.protections.stop_per_pair(pair, side=side) if prot_trig: - msg = {'type': RPCMessageType.PROTECTION_TRIGGER, } - msg.update(prot_trig.to_json()) + msg: RPCProtectionMsg = { + 'type': RPCMessageType.PROTECTION_TRIGGER, + 'base_currency': self.exchange.get_pair_base_currency(prot_trig.pair), + **prot_trig.to_json() # type: ignore + } self.rpc.send_msg(msg) prot_trig_glb = self.protections.global_stop(side=side) if prot_trig_glb: - msg = {'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, } - msg.update(prot_trig_glb.to_json()) + msg = { + 'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, + 'base_currency': self.exchange.get_pair_base_currency(prot_trig_glb.pair), + **prot_trig_glb.to_json() # type: ignore + } self.rpc.send_msg(msg) def apply_fee_conditional(self, trade: Trade, trade_base_currency: str, diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 9d9cf38d7..0cd5c6ffd 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -6,8 +6,7 @@ import logging import re from datetime import datetime from pathlib import Path -from typing import Any, Dict, Iterator, List, Mapping, Optional, Union -from typing.io import IO +from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union from urllib.parse import urlparse import orjson @@ -81,7 +80,7 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = else: if log: logger.info(f'dumping json to "{filename}"') - with open(filename, 'w') as fp: + with filename.open('w') as fp: rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE) logger.debug(f'done json to "{filename}"') @@ -98,12 +97,12 @@ def file_dump_joblib(filename: Path, data: Any, log: bool = True) -> None: if log: logger.info(f'dumping joblib to "{filename}"') - with open(filename, 'wb') as fp: + with filename.open('wb') as fp: joblib.dump(data, fp) logger.debug(f'done joblib dump to "{filename}"') -def json_load(datafile: IO) -> Any: +def json_load(datafile: Union[gzip.GzipFile, TextIO]) -> Any: """ load data with rapidjson Use this to have a consistent experience, @@ -112,7 +111,7 @@ def json_load(datafile: IO) -> Any: return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE) -def file_load_json(file): +def file_load_json(file: Path): if file.suffix != ".gz": gzipfile = file.with_suffix(file.suffix + '.gz') @@ -125,7 +124,7 @@ def file_load_json(file): pairdata = json_load(datafile) elif file.is_file(): logger.debug(f"Loading historical data from file {file}") - with open(file) as datafile: + with file.open() as datafile: pairdata = json_load(datafile) else: return None diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py index d9d270072..f34bbffef 100644 --- a/freqtrade/optimize/backtest_caching.py +++ b/freqtrade/optimize/backtest_caching.py @@ -29,7 +29,7 @@ def get_strategy_run_id(strategy) -> str: # Include _ft_params_from_file - so changing parameter files cause cache eviction digest.update(rapidjson.dumps( strategy._ft_params_from_file, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) - with open(strategy.__file__, 'rb') as fp: + with Path(strategy.__file__).open('rb') as fp: digest.update(fp.read()) return digest.hexdigest().lower() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 065a88f40..c7b2a0d3c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -93,7 +93,7 @@ class Backtesting: if self.config.get('strategy_list'): if self.config.get('freqai', {}).get('enabled', False): logger.warning("Using --strategy-list with FreqAI REQUIRES all strategies " - "to have identical populate_any_indicators.") + "to have identical feature_engineering_* functions.") for strat in list(self.config['strategy_list']): stratconf = deepcopy(self.config) stratconf['strategy'] = strat @@ -203,9 +203,10 @@ class Backtesting: # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + # Update can_short flag + self._can_short = self.trading_mode != TradingMode.SPOT and strategy.can_short self.strategy.ft_bot_start() - strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): @@ -440,11 +441,8 @@ class Backtesting: side_1 * abs(self.strategy.trailing_stop_positive / leverage))) else: # Worst case: price ticks tiny bit above open and dives down. - stop_rate = row[OPEN_IDX] * (1 - side_1 * abs(trade.stop_loss_pct / leverage)) - if is_short: - assert stop_rate > row[LOW_IDX] - else: - assert stop_rate < row[HIGH_IDX] + stop_rate = row[OPEN_IDX] * (1 - side_1 * abs( + (trade.stop_loss_pct or 0.0) / leverage)) # Limit lower-end to candle low to avoid exits below the low. # This still remains "worst case" - but "worst realistic case". @@ -472,7 +470,7 @@ class Backtesting: # - (Expected abs profit - open_rate - open_fee) / (fee_close -1) roi_rate = trade.open_rate * roi / leverage open_fee_rate = side_1 * trade.open_rate * (1 + side_1 * trade.fee_open) - close_rate = -(roi_rate + open_fee_rate) / (trade.fee_close - side_1 * 1) + close_rate = -(roi_rate + open_fee_rate) / ((trade.fee_close or 0.0) - side_1 * 1) if is_short: is_new_roi = row[OPEN_IDX] < close_rate else: @@ -525,7 +523,7 @@ class Backtesting: max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None)( + default_retval=None, supress_error=True)( trade=trade, # type: ignore[arg-type] current_time=current_date, current_rate=current_rate, current_profit=current_profit, min_stake=min_stake, @@ -563,7 +561,7 @@ class Backtesting: pos_trade = self._get_exit_for_signal(trade, row, exit_, amount) if pos_trade is not None: order = pos_trade.orders[-1] - if self._get_order_filled(order.price, row): + if self._get_order_filled(order.ft_price, row): order.close_bt_order(current_date, trade) trade.recalc_trade_from_orders() self.wallets.update() @@ -664,6 +662,7 @@ class Backtesting: side=trade.exit_side, order_type=order_type, status="open", + ft_price=close_rate, price=close_rate, average=close_rate, amount=amount, @@ -742,12 +741,12 @@ class Backtesting: proposed_leverage=1.0, max_leverage=max_leverage, side=direction, entry_tag=entry_tag, - ) if self._can_short else 1.0 + ) if self.trading_mode != TradingMode.SPOT else 1.0 # Cap leverage between 1.0 and max_leverage. leverage = min(max(leverage, 1.0), max_leverage) min_stake_amount = self.exchange.get_min_pair_stake_amount( - pair, propose_rate, -0.05, leverage=leverage) or 0 + pair, propose_rate, -0.05 if not pos_adjust else 0.0, leverage=leverage) or 0 max_stake_amount = self.exchange.get_max_pair_stake_amount( pair, propose_rate, leverage=leverage) stake_available = self.wallets.get_available_stake_amount() @@ -887,6 +886,7 @@ class Backtesting: order_date=current_time, order_filled_date=current_time, order_update_date=current_time, + ft_price=propose_rate, price=propose_rate, average=propose_rate, amount=amount, @@ -895,7 +895,7 @@ class Backtesting: cost=stake_amount + trade.fee_open, ) trade.orders.append(order) - if pos_adjust and self._get_order_filled(order.price, row): + if pos_adjust and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) else: trade.open_order_id = str(self.order_id_counter) @@ -1008,15 +1008,15 @@ class Backtesting: # only check on new candles for open entry orders if order.side == trade.entry_side and current_time > order.order_date_utc: requested_rate = strategy_safe_wrapper(self.strategy.adjust_entry_price, - default_retval=order.price)( + default_retval=order.ft_price)( trade=trade, # type: ignore[arg-type] order=order, pair=trade.pair, current_time=current_time, - proposed_rate=row[OPEN_IDX], current_order_rate=order.price, + proposed_rate=row[OPEN_IDX], current_order_rate=order.ft_price, entry_tag=trade.enter_tag, side=trade.trade_direction ) # default value is current order price # cancel existing order whenever a new rate is requested (or None) - if requested_rate == order.price: + if requested_rate == order.ft_price: # assumption: there can't be multiple open entry orders at any given time return False else: @@ -1028,8 +1028,12 @@ class Backtesting: if requested_rate: self._enter_trade(pair=trade.pair, row=row, trade=trade, requested_rate=requested_rate, - requested_stake=(order.remaining * order.price / trade.leverage), + requested_stake=( + order.safe_remaining * order.ft_price / trade.leverage), direction='short' if trade.is_short else 'long') + # Delete trade if no successful entries happened (if placing the new order failed) + if trade.open_order_id is None and trade.nr_of_successful_entries == 0: + return True self.replaced_entry_orders += 1 else: # assumption: there can't be multiple open entry orders at any given time @@ -1095,7 +1099,7 @@ class Backtesting: for trade in list(LocalTrade.bt_trades_open_pp[pair]): # 3. Process entry orders. order = trade.select_order(trade.entry_side, is_open=True) - if order and self._get_order_filled(order.price, row): + if order and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) trade.open_order_id = None self.wallets.update() @@ -1106,7 +1110,7 @@ class Backtesting: # 5. Process exit orders. order = trade.select_order(trade.exit_side, is_open=True) - if order and self._get_order_filled(order.price, row): + if order and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_time, trade) trade.open_order_id = None sub_trade = order.safe_amount_after_fee != trade.amount @@ -1115,7 +1119,7 @@ class Backtesting: trade.recalc_trade_from_orders() else: trade.close_date = current_time - trade.close(order.price, show_msg=False) + trade.close(order.ft_price, show_msg=False) # logger.debug(f"{pair} - Backtesting exit {trade}") LocalTrade.close_bt_trade(trade) @@ -1155,6 +1159,8 @@ class Backtesting: while current_time <= end_date: open_trade_count_start = LocalTrade.bt_open_open_trade_count self.check_abort() + strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( + current_time=current_time) for i, pair in enumerate(data): row_index = indexes[pair] row = self.validate_row(data, pair, row_index, current_time) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index cf0650f7d..e2133a956 100644 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -1,4 +1,3 @@ -import io import logging from copy import deepcopy from datetime import datetime, timezone @@ -464,8 +463,8 @@ class HyperoptTools(): return try: - io.open(csv_file, 'w+').close() - except IOError: + Path(csv_file).open('w+').close() + except OSError: logger.error(f"Failed to create CSV file: {csv_file}") return diff --git a/freqtrade/persistence/base.py b/freqtrade/persistence/base.py index fb2d561e1..fc2dac75e 100644 --- a/freqtrade/persistence/base.py +++ b/freqtrade/persistence/base.py @@ -1,7 +1,9 @@ -from typing import Any - -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase, Session, scoped_session -_DECL_BASE: Any = declarative_base() +SessionType = scoped_session[Session] + + +class ModelBase(DeclarativeBase): + pass diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 7f851322e..2315c0acc 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -2,6 +2,9 @@ This module contains the class to persist trades into SQLite """ import logging +import threading +from contextvars import ContextVar +from typing import Any, Dict, Final, Optional from sqlalchemy import create_engine, inspect from sqlalchemy.exc import NoSuchModuleError @@ -9,7 +12,7 @@ from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade @@ -18,6 +21,22 @@ from freqtrade.persistence.trade_model import Order, Trade logger = logging.getLogger(__name__) +REQUEST_ID_CTX_KEY: Final[str] = 'request_id' +_request_id_ctx_var: ContextVar[Optional[str]] = ContextVar(REQUEST_ID_CTX_KEY, default=None) + + +def get_request_or_thread_id() -> Optional[str]: + """ + Helper method to get either async context (for fastapi requests), or thread id + """ + id = _request_id_ctx_var.get() + if id is None: + # when not in request context - use thread id + id = str(threading.current_thread().ident) + + return id + + _SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls' @@ -29,7 +48,7 @@ def init_db(db_url: str) -> None: :param db_url: Database to use :return: None """ - kwargs = {} + kwargs: Dict[str, Any] = {} if db_url == 'sqlite:///': raise OperationalException( @@ -52,12 +71,12 @@ def init_db(db_url: str) -> None: # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope # Scoped sessions proxy requests to the appropriate thread-local session. - # We should use the scoped_session object - not a seperately initialized version - Trade._session = scoped_session(sessionmaker(bind=engine, autoflush=False)) - Trade.query = Trade._session.query_property() - Order.query = Trade._session.query_property() - PairLock.query = Trade._session.query_property() + # Since we also use fastAPI, we need to make it aware of the request id, too + Trade.session = scoped_session(sessionmaker( + bind=engine, autoflush=False), scopefunc=get_request_or_thread_id) + Order.session = Trade.session + PairLock.session = Trade.session previous_tables = inspect(engine).get_table_names() - _DECL_BASE.metadata.create_all(engine) - check_migrate(engine, decl_base=_DECL_BASE, previous_tables=previous_tables) + ModelBase.metadata.create_all(engine) + check_migrate(engine, decl_base=ModelBase, previous_tables=previous_tables) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 938cd14bc..1b254c2b2 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -1,33 +1,34 @@ from datetime import datetime, timezone -from typing import Any, Dict, Optional +from typing import Any, ClassVar, Dict, Optional -from sqlalchemy import Boolean, Column, DateTime, Integer, String, or_ -from sqlalchemy.orm import Query +from sqlalchemy import ScalarResult, String, or_, select +from sqlalchemy.orm import Mapped, mapped_column from freqtrade.constants import DATETIME_PRINT_FORMAT -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase, SessionType -class PairLock(_DECL_BASE): +class PairLock(ModelBase): """ Pair Locks database model. """ __tablename__ = 'pairlocks' + session: ClassVar[SessionType] - id = Column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(primary_key=True) - pair = Column(String(25), nullable=False, index=True) + pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # lock direction - long, short or * (for both) - side = Column(String(25), nullable=False, default="*") - reason = Column(String(255), nullable=True) + side: Mapped[str] = mapped_column(String(25), nullable=False, default="*") + reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time = Column(DateTime(), nullable=False) + lock_time: Mapped[datetime] = mapped_column(nullable=False) # Time until the pair is locked (end time) - lock_end_time = Column(DateTime(), nullable=False, index=True) + lock_end_time: Mapped[datetime] = mapped_column(nullable=False, index=True) - active = Column(Boolean, nullable=False, default=True, index=True) + active: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) - def __repr__(self): + def __repr__(self) -> str: lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT) return ( @@ -35,7 +36,8 @@ class PairLock(_DECL_BASE): f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') @staticmethod - def query_pair_locks(pair: Optional[str], now: datetime, side: str = '*') -> Query: + def query_pair_locks( + pair: Optional[str], now: datetime, side: str = '*') -> ScalarResult['PairLock']: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -51,9 +53,11 @@ class PairLock(_DECL_BASE): else: filters.append(PairLock.side == '*') - return PairLock.query.filter( - *filters - ) + return PairLock.session.scalars(select(PairLock).filter(*filters)) + + @staticmethod + def get_all_locks() -> ScalarResult['PairLock']: + return PairLock.session.scalars(select(PairLock)) def to_json(self) -> Dict[str, Any]: return { diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 4485bb88e..29169a50d 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -1,6 +1,8 @@ import logging from datetime import datetime, timezone -from typing import List, Optional +from typing import List, Optional, Sequence + +from sqlalchemy import select from freqtrade.exchange import timeframe_to_next_date from freqtrade.persistence.models import PairLock @@ -51,15 +53,15 @@ class PairLocks(): active=True ) if PairLocks.use_db: - PairLock.query.session.add(lock) - PairLock.query.session.commit() + PairLock.session.add(lock) + PairLock.session.commit() else: PairLocks.locks.append(lock) return lock @staticmethod - def get_pair_locks( - pair: Optional[str], now: Optional[datetime] = None, side: str = '*') -> List[PairLock]: + def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None, + side: str = '*') -> Sequence[PairLock]: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -106,7 +108,7 @@ class PairLocks(): for lock in locks: lock.active = False if PairLocks.use_db: - PairLock.query.session.commit() + PairLock.session.commit() @staticmethod def unlock_reason(reason: str, now: Optional[datetime] = None) -> None: @@ -126,15 +128,15 @@ class PairLocks(): PairLock.active.is_(True), PairLock.reason == reason ] - locks = PairLock.query.filter(*filters) + locks = PairLock.session.scalars(select(PairLock).filter(*filters)).all() for lock in locks: logger.info(f"Releasing lock for {lock.pair} with reason '{reason}'.") lock.active = False - PairLock.query.session.commit() + PairLock.session.commit() else: # used in backtesting mode; don't show log messages for speed - locks = PairLocks.get_pair_locks(None) - for lock in locks: + locksb = PairLocks.get_pair_locks(None) + for lock in locksb: if lock.reason == reason: lock.active = False @@ -165,11 +167,11 @@ class PairLocks(): ) @staticmethod - def get_all_locks() -> List[PairLock]: + def get_all_locks() -> Sequence[PairLock]: """ Return all locks, also locks with expired end date """ if PairLocks.use_db: - return PairLock.query.all() + return PairLock.get_all_locks().all() else: return PairLocks.locks diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index c84fcec9e..17117d436 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -5,11 +5,11 @@ import logging from collections import defaultdict from datetime import datetime, timedelta, timezone from math import isclose -from typing import Any, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional, Sequence, cast -from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String, - UniqueConstraint, desc, func) -from sqlalchemy.orm import Query, lazyload, relationship +from sqlalchemy import (Enum, Float, ForeignKey, Integer, ScalarResult, Select, String, + UniqueConstraint, desc, func, select) +from sqlalchemy.orm import Mapped, lazyload, mapped_column, relationship from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, BuySell, LongShort) @@ -17,14 +17,14 @@ from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import amount_to_contract_precision, price_to_precision from freqtrade.leverage import interest -from freqtrade.persistence.base import _DECL_BASE +from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.util import FtPrecise logger = logging.getLogger(__name__) -class Order(_DECL_BASE): +class Order(ModelBase): """ Order database model Keeps a record of all orders placed on the exchange @@ -36,41 +36,43 @@ class Order(_DECL_BASE): Mirrors CCXT Order structure """ __tablename__ = 'orders' + session: ClassVar[SessionType] + # Uniqueness should be ensured over pair, order_id # its likely that order_id is unique per Pair on some exchanges. __table_args__ = (UniqueConstraint('ft_pair', 'order_id', name="_order_pair_order_id"),) - id = Column(Integer, primary_key=True) - ft_trade_id = Column(Integer, ForeignKey('trades.id'), index=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + ft_trade_id: Mapped[int] = mapped_column(Integer, ForeignKey('trades.id'), index=True) - trade = relationship("Trade", back_populates="orders") + trade: Mapped[List["Trade"]] = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side = Column(String(25), nullable=False) - ft_pair = Column(String(25), nullable=False) - ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - ft_amount = Column(Float(), nullable=False) - ft_price = Column(Float(), nullable=False) + ft_order_side: Mapped[str] = mapped_column(String(25), nullable=False) + ft_pair: Mapped[str] = mapped_column(String(25), nullable=False) + ft_is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) + ft_amount: Mapped[float] = mapped_column(Float(), nullable=False) + ft_price: Mapped[float] = mapped_column(Float(), nullable=False) - order_id = Column(String(255), nullable=False, index=True) - status = Column(String(255), nullable=True) - symbol = Column(String(25), nullable=True) - order_type = Column(String(50), nullable=True) - side = Column(String(25), nullable=True) - price = Column(Float(), nullable=True) - average = Column(Float(), nullable=True) - amount = Column(Float(), nullable=True) - filled = Column(Float(), nullable=True) - remaining = Column(Float(), nullable=True) - cost = Column(Float(), nullable=True) - stop_price = Column(Float(), nullable=True) - order_date = Column(DateTime(), nullable=True, default=datetime.utcnow) - order_filled_date = Column(DateTime(), nullable=True) - order_update_date = Column(DateTime(), nullable=True) + order_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) + status: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + symbol: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) + # TODO: type: order_type type is Optional[str] + order_type: Mapped[str] = mapped_column(String(50), nullable=True) + side: Mapped[str] = mapped_column(String(25), nullable=True) + price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + average: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + amount: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + filled: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + remaining: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + stop_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + order_date: Mapped[datetime] = mapped_column(nullable=True, default=datetime.utcnow) + order_filled_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) + order_update_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) + funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) - funding_fee = Column(Float(), nullable=True) - - ft_fee_base = Column(Float(), nullable=True) + ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: @@ -96,6 +98,10 @@ class Order(_DECL_BASE): def safe_filled(self) -> float: return self.filled if self.filled is not None else self.amount or 0.0 + @property + def safe_cost(self) -> float: + return self.cost or 0.0 + @property def safe_remaining(self) -> float: return ( @@ -113,8 +119,9 @@ class Order(_DECL_BASE): def __repr__(self): - return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, ' - f'side={self.side}, order_type={self.order_type}, status={self.status})') + return (f"Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, " + f"side={self.side}, filled={self.safe_filled}, price={self.safe_price}, " + f"order_type={self.order_type}, status={self.status})") def update_from_ccxt_object(self, order): """ @@ -151,7 +158,7 @@ class Order(_DECL_BASE): self.order_update_date = datetime.now(timezone.utc) def to_ccxt_object(self) -> Dict[str, Any]: - order = { + order: Dict[str, Any] = { 'id': self.order_id, 'symbol': self.ft_pair, 'price': self.price, @@ -213,7 +220,7 @@ class Order(_DECL_BASE): # Assumes backtesting will use date_last_filled_utc to calculate future funding fees. self.funding_fee = trade.funding_fees - if (self.ft_order_side == trade.entry_side): + if (self.ft_order_side == trade.entry_side and self.price): trade.open_rate = self.price trade.recalc_trade_from_orders() trade.adjust_stop_loss(trade.open_rate, trade.stop_loss_pct, refresh=True) @@ -255,12 +262,12 @@ class Order(_DECL_BASE): return o @staticmethod - def get_open_orders() -> List['Order']: + def get_open_orders() -> Sequence['Order']: """ Retrieve open orders from the database :return: List of open orders """ - return Order.query.filter(Order.ft_is_open.is_(True)).all() + return Order.session.scalars(select(Order).filter(Order.ft_is_open.is_(True))).all() @staticmethod def order_by_id(order_id: str) -> Optional['Order']: @@ -268,7 +275,7 @@ class Order(_DECL_BASE): Retrieve order based on order_id :return: Order or None """ - return Order.query.filter(Order.order_id == order_id).first() + return Order.session.scalars(select(Order).filter(Order.order_id == order_id)).first() class LocalTrade(): @@ -293,15 +300,15 @@ class LocalTrade(): exchange: str = '' pair: str = '' - base_currency: str = '' - stake_currency: str = '' + base_currency: Optional[str] = '' + stake_currency: Optional[str] = '' is_open: bool = True fee_open: float = 0.0 fee_open_cost: Optional[float] = None - fee_open_currency: str = '' - fee_close: float = 0.0 + fee_open_currency: Optional[str] = '' + fee_close: Optional[float] = 0.0 fee_close_cost: Optional[float] = None - fee_close_currency: str = '' + fee_close_currency: Optional[str] = '' open_rate: float = 0.0 open_rate_requested: Optional[float] = None # open_trade_value - calculated via _calc_open_trade_value @@ -311,7 +318,7 @@ class LocalTrade(): close_profit: Optional[float] = None close_profit_abs: Optional[float] = None stake_amount: float = 0.0 - max_stake_amount: float = 0.0 + max_stake_amount: Optional[float] = 0.0 amount: float = 0.0 amount_requested: Optional[float] = None open_date: datetime @@ -320,9 +327,9 @@ class LocalTrade(): # absolute value of the stop loss stop_loss: float = 0.0 # percentage value of the stop loss - stop_loss_pct: float = 0.0 + stop_loss_pct: Optional[float] = 0.0 # absolute value of the initial stop loss - initial_stop_loss: float = 0.0 + initial_stop_loss: Optional[float] = 0.0 # percentage value of the initial stop loss initial_stop_loss_pct: Optional[float] = None # stoploss order id which is on exchange @@ -330,12 +337,12 @@ class LocalTrade(): # last update time of the stoploss order on exchange stoploss_last_update: Optional[datetime] = None # absolute value of the highest reached price - max_rate: float = 0.0 + max_rate: Optional[float] = None # Lowest price reached - min_rate: float = 0.0 - exit_reason: str = '' - exit_order_status: str = '' - strategy: str = '' + min_rate: Optional[float] = None + exit_reason: Optional[str] = '' + exit_order_status: Optional[str] = '' + strategy: Optional[str] = '' enter_tag: Optional[str] = None timeframe: Optional[int] = None @@ -511,6 +518,8 @@ class LocalTrade(): 'close_timestamp': int(self.close_date.replace( tzinfo=timezone.utc).timestamp() * 1000) if self.close_date else None, 'realized_profit': self.realized_profit or 0.0, + # Close-profit corresponds to relative realized_profit ratio + 'realized_profit_ratio': self.close_profit or None, 'close_rate': self.close_rate, 'close_rate_requested': self.close_rate_requested, 'close_profit': self.close_profit, # Deprecated @@ -551,6 +560,9 @@ class LocalTrade(): 'trading_mode': self.trading_mode, 'funding_fees': self.funding_fees, 'open_order_id': self.open_order_id, + 'amount_precision': self.amount_precision, + 'price_precision': self.price_precision, + 'precision_mode': self.precision_mode, 'orders': orders, } @@ -592,7 +604,7 @@ class LocalTrade(): self.stop_loss_pct = -1 * abs(percent) - def adjust_stop_loss(self, current_price: float, stoploss: float, + def adjust_stop_loss(self, current_price: float, stoploss: Optional[float], initial: bool = False, refresh: bool = False) -> None: """ This adjusts the stop loss to it's most recently observed setting @@ -601,7 +613,7 @@ class LocalTrade(): :param initial: Called to initiate stop_loss. Skips everything if self.stop_loss is already set. """ - if initial and not (self.stop_loss is None or self.stop_loss == 0): + if stoploss is None or (initial and not (self.stop_loss is None or self.stop_loss == 0)): # Don't modify if called with initial and nothing to do return refresh = True if refresh and self.nr_of_successful_entries == 1 else False @@ -640,7 +652,7 @@ class LocalTrade(): f"initial_stop_loss={self.initial_stop_loss:.8f}, " f"stop_loss={self.stop_loss:.8f}. " f"Trailing stoploss saved us: " - f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") + f"{float(self.stop_loss) - float(self.initial_stop_loss or 0.0):.8f}.") def update_trade(self, order: Order) -> None: """ @@ -792,10 +804,10 @@ class LocalTrade(): return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours) - def _calc_base_close(self, amount: FtPrecise, rate: float, fee: float) -> FtPrecise: + def _calc_base_close(self, amount: FtPrecise, rate: float, fee: Optional[float]) -> FtPrecise: close_trade = amount * FtPrecise(rate) - fees = close_trade * FtPrecise(fee) + fees = close_trade * FtPrecise(fee or 0.0) if self.is_short: return close_trade + fees @@ -1059,10 +1071,14 @@ class LocalTrade(): return len(self.select_filled_orders('sell')) @property - def sell_reason(self) -> str: + def sell_reason(self) -> Optional[str]: """ DEPRECATED! Please use exit_reason instead.""" return self.exit_reason + @property + def safe_close_rate(self) -> float: + return self.close_rate or self.close_rate_requested or 0.0 + @staticmethod def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, open_date: Optional[datetime] = None, @@ -1074,6 +1090,11 @@ class LocalTrade(): In live mode, converts the filter to a database query and returns all rows In Backtest mode, uses filters on Trade.trades to get the result. + :param pair: Filter by pair + :param is_open: Filter by open/closed status + :param open_date: Filter by open_date (filters via trade.open_date > input) + :param close_date: Filter by close_date (filters via trade.close_date > input) + Will implicitly only return closed trades. :return: unsorted List[Trade] """ @@ -1124,7 +1145,7 @@ class LocalTrade(): @staticmethod def get_open_trades() -> List[Any]: """ - Query trades from persistence layer + Retrieve open trades """ return Trade.get_trades_proxy(is_open=True) @@ -1134,7 +1155,9 @@ class LocalTrade(): get open trade count """ if Trade.use_db: - return Trade.query.filter(Trade.is_open.is_(True)).count() + return Trade.session.execute( + select(func.count(Trade.id)).filter(Trade.is_open.is_(True)) + ).scalar_one() else: return LocalTrade.bt_open_open_trade_count @@ -1159,7 +1182,7 @@ class LocalTrade(): logger.info(f"New stoploss: {trade.stop_loss}.") -class Trade(_DECL_BASE, LocalTrade): +class Trade(ModelBase, LocalTrade): """ Trade database model. Also handles updating and querying trades @@ -1167,79 +1190,97 @@ class Trade(_DECL_BASE, LocalTrade): Note: Fields must be aligned with LocalTrade class """ __tablename__ = 'trades' + session: ClassVar[SessionType] use_db: bool = True - id = Column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) # type: ignore - orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", - lazy="selectin", innerjoin=True) + orders: Mapped[List[Order]] = relationship( + "Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", + innerjoin=True) # type: ignore - exchange = Column(String(25), nullable=False) - pair = Column(String(25), nullable=False, index=True) - base_currency = Column(String(25), nullable=True) - stake_currency = Column(String(25), nullable=True) - is_open = Column(Boolean, nullable=False, default=True, index=True) - fee_open = Column(Float(), nullable=False, default=0.0) - fee_open_cost = Column(Float(), nullable=True) - fee_open_currency = Column(String(25), nullable=True) - fee_close = Column(Float(), nullable=False, default=0.0) - fee_close_cost = Column(Float(), nullable=True) - fee_close_currency = Column(String(25), nullable=True) - open_rate: float = Column(Float()) - open_rate_requested = Column(Float()) + exchange: Mapped[str] = mapped_column(String(25), nullable=False) # type: ignore + pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # type: ignore + base_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore + stake_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore + is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) # type: ignore + fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) # type: ignore + fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + fee_open_currency: Mapped[Optional[str]] = mapped_column( + String(25), nullable=True) # type: ignore + fee_close: Mapped[Optional[float]] = mapped_column( + Float(), nullable=False, default=0.0) # type: ignore + fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + fee_close_currency: Mapped[Optional[str]] = mapped_column( + String(25), nullable=True) # type: ignore + open_rate: Mapped[float] = mapped_column(Float()) # type: ignore + open_rate_requested: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # open_trade_value - calculated via _calc_open_trade_value - open_trade_value = Column(Float()) - close_rate: Optional[float] = Column(Float()) - close_rate_requested = Column(Float()) - realized_profit = Column(Float(), default=0.0) - close_profit = Column(Float()) - close_profit_abs = Column(Float()) - stake_amount = Column(Float(), nullable=False) - max_stake_amount = Column(Float()) - amount = Column(Float()) - amount_requested = Column(Float()) - open_date = Column(DateTime(), nullable=False, default=datetime.utcnow) - close_date = Column(DateTime()) - open_order_id = Column(String(255)) + open_trade_value: Mapped[float] = mapped_column(Float(), nullable=True) # type: ignore + close_rate: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + close_rate_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + realized_profit: Mapped[float] = mapped_column( + Float(), default=0.0, nullable=True) # type: ignore + close_profit: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) # type: ignore + max_stake_amount: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + amount: Mapped[float] = mapped_column(Float()) # type: ignore + amount_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore + open_date: Mapped[datetime] = mapped_column( + nullable=False, default=datetime.utcnow) # type: ignore + close_date: Mapped[Optional[datetime]] = mapped_column() # type: ignore + open_order_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) # type: ignore # absolute value of the stop loss - stop_loss = Column(Float(), nullable=True, default=0.0) + stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # type: ignore # percentage value of the stop loss - stop_loss_pct = Column(Float(), nullable=True) + stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # absolute value of the initial stop loss - initial_stop_loss = Column(Float(), nullable=True, default=0.0) + initial_stop_loss: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=0.0) # type: ignore # percentage value of the initial stop loss - initial_stop_loss_pct = Column(Float(), nullable=True) + initial_stop_loss_pct: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # stoploss order id which is on exchange - stoploss_order_id = Column(String(255), nullable=True, index=True) + stoploss_order_id: Mapped[Optional[str]] = mapped_column( + String(255), nullable=True, index=True) # type: ignore # last update time of the stoploss order on exchange - stoploss_last_update = Column(DateTime(), nullable=True) + stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # type: ignore # absolute value of the highest reached price - max_rate = Column(Float(), nullable=True, default=0.0) + max_rate: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=0.0) # type: ignore # Lowest price reached - min_rate = Column(Float(), nullable=True) - exit_reason = Column(String(100), nullable=True) - exit_order_status = Column(String(100), nullable=True) - strategy = Column(String(100), nullable=True) - enter_tag = Column(String(100), nullable=True) - timeframe = Column(Integer, nullable=True) + min_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + exit_reason: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + exit_order_status: Mapped[Optional[str]] = mapped_column( + String(100), nullable=True) # type: ignore + strategy: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + enter_tag: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore + timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore - trading_mode = Column(Enum(TradingMode), nullable=True) - amount_precision = Column(Float(), nullable=True) - price_precision = Column(Float(), nullable=True) - precision_mode = Column(Integer, nullable=True) - contract_size = Column(Float(), nullable=True) + trading_mode: Mapped[TradingMode] = mapped_column( + Enum(TradingMode), nullable=True) # type: ignore + amount_precision: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore + price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore + precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore + contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # Leverage trading properties - leverage = Column(Float(), nullable=True, default=1.0) - is_short = Column(Boolean, nullable=False, default=False) - liquidation_price = Column(Float(), nullable=True) + leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) # type: ignore + is_short: Mapped[bool] = mapped_column(nullable=False, default=False) # type: ignore + liquidation_price: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True) # type: ignore # Margin Trading Properties - interest_rate = Column(Float(), nullable=False, default=0.0) + interest_rate: Mapped[float] = mapped_column( + Float(), nullable=False, default=0.0) # type: ignore # Futures properties - funding_fees = Column(Float(), nullable=True, default=None) + funding_fees: Mapped[Optional[float]] = mapped_column( + Float(), nullable=True, default=None) # type: ignore def __init__(self, **kwargs): super().__init__(**kwargs) @@ -1249,18 +1290,18 @@ class Trade(_DECL_BASE, LocalTrade): def delete(self) -> None: for order in self.orders: - Order.query.session.delete(order) + Order.session.delete(order) - Trade.query.session.delete(self) + Trade.session.delete(self) Trade.commit() @staticmethod def commit(): - Trade.query.session.commit() + Trade.session.commit() @staticmethod def rollback(): - Trade.query.session.rollback() + Trade.session.rollback() @staticmethod def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, @@ -1285,7 +1326,7 @@ class Trade(_DECL_BASE, LocalTrade): trade_filter.append(Trade.close_date > close_date) if is_open is not None: trade_filter.append(Trade.is_open.is_(is_open)) - return Trade.get_trades(trade_filter).all() + return cast(List[LocalTrade], Trade.get_trades(trade_filter).all()) else: return LocalTrade.get_trades_proxy( pair=pair, is_open=is_open, @@ -1294,7 +1335,7 @@ class Trade(_DECL_BASE, LocalTrade): ) @staticmethod - def get_trades(trade_filter=None, include_orders: bool = True) -> Query: + def get_trades_query(trade_filter=None, include_orders: bool = True) -> Select: """ Helper function to query Trades using filters. NOTE: Not supported in Backtesting. @@ -1309,22 +1350,35 @@ class Trade(_DECL_BASE, LocalTrade): if trade_filter is not None: if not isinstance(trade_filter, list): trade_filter = [trade_filter] - this_query = Trade.query.filter(*trade_filter) + this_query = select(Trade).filter(*trade_filter) else: - this_query = Trade.query + this_query = select(Trade) if not include_orders: # Don't load order relations # Consider using noload or raiseload instead of lazyload this_query = this_query.options(lazyload(Trade.orders)) return this_query + @staticmethod + def get_trades(trade_filter=None, include_orders: bool = True) -> ScalarResult['Trade']: + """ + Helper function to query Trades using filters. + NOTE: Not supported in Backtesting. + :param trade_filter: Optional filter to apply to trades + Can be either a Filter object, or a List of filters + e.g. `(trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True),])` + e.g. `(trade_filter=Trade.id == trade_id)` + :return: unsorted query object + """ + return Trade.session.scalars(Trade.get_trades_query(trade_filter, include_orders)) + @staticmethod def get_open_order_trades() -> List['Trade']: """ Returns all open trades NOTE: Not supported in Backtesting. """ - return Trade.get_trades(Trade.open_order_id.isnot(None)).all() + return cast(List[Trade], Trade.get_trades(Trade.open_order_id.isnot(None)).all()) @staticmethod def get_open_trades_without_assigned_fees(): @@ -1354,11 +1408,12 @@ class Trade(_DECL_BASE, LocalTrade): Retrieves total realized profit """ if Trade.use_db: - total_profit = Trade.query.with_entities( - func.sum(Trade.close_profit_abs)).filter(Trade.is_open.is_(False)).scalar() + total_profit: float = Trade.session.execute( + select(func.sum(Trade.close_profit_abs)).filter(Trade.is_open.is_(False)) + ).scalar_one() else: - total_profit = sum( - t.close_profit_abs for t in LocalTrade.get_trades_proxy(is_open=False)) + total_profit = sum(t.close_profit_abs # type: ignore + for t in LocalTrade.get_trades_proxy(is_open=False)) return total_profit or 0 @staticmethod @@ -1368,8 +1423,9 @@ class Trade(_DECL_BASE, LocalTrade): in stake currency """ if Trade.use_db: - total_open_stake_amount = Trade.query.with_entities( - func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True)).scalar() + total_open_stake_amount = Trade.session.scalar( + select(func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True)) + ) else: total_open_stake_amount = sum( t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True)) @@ -1381,19 +1437,22 @@ class Trade(_DECL_BASE, LocalTrade): Returns List of dicts containing all Trades, including profit and trade count NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if minutes: start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes) filters.append(Trade.close_date >= start_date) - pair_rates = Trade.query.with_entities( - Trade.pair, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(*filters)\ - .group_by(Trade.pair) \ - .order_by(desc('profit_sum_abs')) \ - .all() + + pair_rates = Trade.session.execute( + select( + Trade.pair, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters) + .group_by(Trade.pair) + .order_by(desc('profit_sum_abs')) + ).all() + return [ { 'pair': pair, @@ -1414,19 +1473,20 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) - enter_tag_perf = Trade.query.with_entities( - Trade.enter_tag, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(*filters)\ - .group_by(Trade.enter_tag) \ - .order_by(desc('profit_sum_abs')) \ - .all() + enter_tag_perf = Trade.session.execute( + select( + Trade.enter_tag, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters) + .group_by(Trade.enter_tag) + .order_by(desc('profit_sum_abs')) + ).all() return [ { @@ -1447,19 +1507,19 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) - - sell_tag_perf = Trade.query.with_entities( - Trade.exit_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(*filters)\ - .group_by(Trade.exit_reason) \ - .order_by(desc('profit_sum_abs')) \ - .all() + sell_tag_perf = Trade.session.execute( + select( + Trade.exit_reason, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters) + .group_by(Trade.exit_reason) + .order_by(desc('profit_sum_abs')) + ).all() return [ { @@ -1480,21 +1540,21 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ - filters = [Trade.is_open.is_(False)] + filters: List = [Trade.is_open.is_(False)] if (pair is not None): filters.append(Trade.pair == pair) - - mix_tag_perf = Trade.query.with_entities( - Trade.id, - Trade.enter_tag, - Trade.exit_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(*filters)\ - .group_by(Trade.id) \ - .order_by(desc('profit_sum_abs')) \ - .all() + mix_tag_perf = Trade.session.execute( + select( + Trade.id, + Trade.enter_tag, + Trade.exit_reason, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters) + .group_by(Trade.id) + .order_by(desc('profit_sum_abs')) + ).all() return_list: List[Dict] = [] for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: @@ -1530,11 +1590,15 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. :returns: Tuple containing (pair, profit_sum) """ - best_pair = Trade.query.with_entities( - Trade.pair, func.sum(Trade.close_profit).label('profit_sum') - ).filter(Trade.is_open.is_(False) & (Trade.close_date >= start_date)) \ - .group_by(Trade.pair) \ - .order_by(desc('profit_sum')).first() + best_pair = Trade.session.execute( + select( + Trade.pair, + func.sum(Trade.close_profit).label('profit_sum') + ).filter(Trade.is_open.is_(False) & (Trade.close_date >= start_date)) + .group_by(Trade.pair) + .order_by(desc('profit_sum')) + ).first() + return best_pair @staticmethod @@ -1544,12 +1608,13 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. :returns: Tuple containing (pair, profit_sum) """ - trading_volume = Order.query.with_entities( - func.sum(Order.cost).label('volume') - ).filter( - Order.order_filled_date >= start_date, - Order.status == 'closed' - ).scalar() + trading_volume = Trade.session.execute( + select( + func.sum(Order.cost).label('volume') + ).filter( + Order.order_filled_date >= start_date, + Order.status == 'closed' + )).scalar_one() return trading_volume @staticmethod @@ -1598,8 +1663,10 @@ class Trade(_DECL_BASE, LocalTrade): stop_loss=data["stop_loss_abs"], stop_loss_pct=data["stop_loss_ratio"], stoploss_order_id=data["stoploss_order_id"], - stoploss_last_update=(datetime.fromtimestamp(data["stoploss_last_update"] // 1000, - tz=timezone.utc) if data["stoploss_last_update"] else None), + stoploss_last_update=( + datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000, + tz=timezone.utc) + if data["stoploss_last_update_timestamp"] else None), initial_stop_loss=data["initial_stop_loss_abs"], initial_stop_loss_pct=data["initial_stop_loss_ratio"], min_rate=data["min_rate"], diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 1b2ee44da..e415c4911 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -1,4 +1,5 @@ import logging +from datetime import datetime, timezone from pathlib import Path from typing import Dict, List, Optional @@ -635,7 +636,7 @@ def load_and_plot_trades(config: Config): exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) IStrategy.dp = DataProvider(config, exchange) strategy.ft_bot_start() - strategy.bot_loop_start() + strategy.bot_loop_start(datetime.now(timezone.utc)) plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count) timerange = plot_elements['timerange'] trades = plot_elements['trades'] diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index b54be1fa7..764c16f1a 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -157,7 +157,7 @@ class RemotePairList(IPairList): file_path = Path(filename) if file_path.exists(): - with open(filename) as json_file: + with file_path.open() as json_file: # Load the JSON data into a dictionary jsonparse = json.load(json_file) diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index 207328d08..d47b68568 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -5,6 +5,7 @@ import logging from typing import Any, Dict, Optional from freqtrade.constants import Config +from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Ticker from freqtrade.plugins.pairlist.IPairList import IPairList @@ -22,6 +23,12 @@ class SpreadFilter(IPairList): self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._enabled = self._max_spread_ratio != 0 + if not self._exchange.get_option('tickers_have_bid_ask'): + raise OperationalException( + f"{self.name} requires exchange to have bid/ask data for tickers, " + "which is not available for the selected exchange / trading mode." + ) + @property def needstickers(self) -> bool: """ diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 58f6ad583..7497b27f1 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -228,24 +228,33 @@ class TradeSchema(BaseModel): fee_close: Optional[float] fee_close_cost: Optional[float] fee_close_currency: Optional[str] + open_date: str open_timestamp: int open_rate: float open_rate_requested: Optional[float] open_trade_value: float + close_date: Optional[str] close_timestamp: Optional[int] close_rate: Optional[float] close_rate_requested: Optional[float] + close_profit: Optional[float] close_profit_pct: Optional[float] close_profit_abs: Optional[float] + profit_ratio: Optional[float] profit_pct: Optional[float] profit_abs: Optional[float] profit_fiat: Optional[float] + + realized_profit: float + realized_profit_ratio: Optional[float] + exit_reason: Optional[str] exit_order_status: Optional[str] + stop_loss_abs: Optional[float] stop_loss_ratio: Optional[float] stop_loss_pct: Optional[float] @@ -255,6 +264,7 @@ class TradeSchema(BaseModel): initial_stop_loss_abs: Optional[float] initial_stop_loss_ratio: Optional[float] initial_stop_loss_pct: Optional[float] + min_rate: Optional[float] max_rate: Optional[float] open_order_id: Optional[str] @@ -266,6 +276,10 @@ class TradeSchema(BaseModel): funding_fees: Optional[float] trading_mode: Optional[TradingMode] + amount_precision: Optional[float] + price_precision: Optional[float] + precision_mode: Optional[int] + class OpenTradeSchema(TradeSchema): stoploss_current_dist: Optional[float] @@ -273,10 +287,11 @@ class OpenTradeSchema(TradeSchema): stoploss_current_dist_ratio: Optional[float] stoploss_entry_dist: Optional[float] stoploss_entry_dist_ratio: Optional[float] - current_profit: float - current_profit_abs: float - current_profit_pct: float current_rate: float + total_profit_abs: float + total_profit_fiat: Optional[float] + total_profit_ratio: Optional[float] + open_order: Optional[str] @@ -300,7 +315,7 @@ class LockModel(BaseModel): lock_timestamp: int pair: str side: str - reason: str + reason: Optional[str] class Locks(BaseModel): @@ -456,5 +471,5 @@ class SysInfo(BaseModel): class Health(BaseModel): - last_process: datetime - last_process_ts: int + last_process: Optional[datetime] + last_process_ts: Optional[int] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 73bdde86b..8ea70bb69 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -42,7 +42,8 @@ logger = logging.getLogger(__name__) # 2.22: Add FreqAI to backtesting # 2.23: Allow plot config request in webserver mode # 2.24: Add cancel_open_order endpoint -API_VERSION = 2.24 +# 2.25: Add several profit values to /status endpoint +API_VERSION = 2.25 # Public API, requires no auth. router_public = APIRouter() @@ -346,4 +347,4 @@ def sysinfo(): @router.get('/health', response_model=Health, tags=['info']) def health(rpc: RPC = Depends(get_rpc)): - return rpc._health() + return rpc.health() diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index aed97367b..f5b1bcd74 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -1,9 +1,11 @@ -from typing import Any, Dict, Iterator, Optional +from typing import Any, AsyncIterator, Dict, Optional +from uuid import uuid4 from fastapi import Depends from freqtrade.enums import RunMode from freqtrade.persistence import Trade +from freqtrade.persistence.models import _request_id_ctx_var from freqtrade.rpc.rpc import RPC, RPCException from .webserver import ApiServer @@ -15,12 +17,19 @@ def get_rpc_optional() -> Optional[RPC]: return None -def get_rpc() -> Optional[Iterator[RPC]]: +async def get_rpc() -> Optional[AsyncIterator[RPC]]: + _rpc = get_rpc_optional() if _rpc: + request_id = str(uuid4()) + ctx_token = _request_id_ctx_var.set(request_id) Trade.rollback() - yield _rpc - Trade.rollback() + try: + yield _rpc + finally: + Trade.session.remove() + _request_id_ctx_var.reset(ctx_token) + else: raise RPCException('Bot is not in the correct state') diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index b53662451..8030e303b 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -13,6 +13,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer from freqtrade.rpc.api_server.ws.message_stream import MessageStream from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler +from freqtrade.rpc.rpc_types import RPCSendMsg logger = logging.getLogger(__name__) @@ -108,7 +109,7 @@ class ApiServer(RPCHandler): cls._has_rpc = False cls._rpc = None - def send_msg(self, msg: Dict[str, Any]) -> None: + def send_msg(self, msg: RPCSendMsg) -> None: """ Publish the message to the message stream """ diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 83bffb779..2b5eb107c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -5,7 +5,7 @@ import logging from abc import abstractmethod from datetime import date, datetime, timedelta, timezone from math import isnan -from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union import arrow import psutil @@ -13,14 +13,15 @@ from dateutil.relativedelta import relativedelta from dateutil.tz import tzlocal from numpy import NAN, inf, int64, mean from pandas import DataFrame, NaT +from sqlalchemy import func, select from freqtrade import __version__ from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config from freqtrade.data.history import load_data from freqtrade.data.metrics import calculate_max_drawdown -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, - TradingMode) +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, SignalDirection, + State, TradingMode) from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.loggers import bufferHandler @@ -29,6 +30,7 @@ from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence.models import PairLock from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.rpc.fiat_convert import CryptoToFiatConverter +from freqtrade.rpc.rpc_types import RPCSendMsg from freqtrade.wallets import PositionWallet, Wallet @@ -78,7 +80,7 @@ class RPCHandler: """ Cleanup pending module resources """ @abstractmethod - def send_msg(self, msg: Dict[str, str]) -> None: + def send_msg(self, msg: RPCSendMsg) -> None: """ Sends a message to all registered rpc modules """ @@ -122,7 +124,8 @@ class RPC: if config['max_open_trades'] != float('inf') else -1), 'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {}, 'stoploss': config.get('stoploss'), - 'stoploss_on_exchange': config.get('stoploss_on_exchange', False), + 'stoploss_on_exchange': config.get('order_types', + {}).get('stoploss_on_exchange', False), 'trailing_stop': config.get('trailing_stop'), 'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), @@ -158,7 +161,7 @@ class RPC: """ # Fetch open trades if trade_ids: - trades: List[Trade] = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all() + trades: Sequence[Trade] = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all() else: trades = Trade.get_open_trades() @@ -169,6 +172,7 @@ class RPC: for trade in trades: order: Optional[Order] = None current_profit_fiat: Optional[float] = None + total_profit_fiat: Optional[float] = None if trade.open_order_id: order = trade.select_order_by_order_id(trade.open_order_id) # calculate profit and send message to user @@ -188,8 +192,14 @@ class RPC: else: # Closed trade ... current_rate = trade.close_rate - current_profit = trade.close_profit - current_profit_abs = trade.close_profit_abs + current_profit = trade.close_profit or 0.0 + current_profit_abs = trade.close_profit_abs or 0.0 + total_profit_abs = trade.realized_profit + current_profit_abs + total_profit_ratio: Optional[float] = None + if trade.max_stake_amount: + total_profit_ratio = ( + (total_profit_abs / trade.max_stake_amount) * trade.leverage + ) # Calculate fiat profit if not isnan(current_profit_abs) and self._fiat_converter: @@ -198,6 +208,11 @@ class RPC: self._freqtrade.config['stake_currency'], self._freqtrade.config['fiat_display_currency'] ) + total_profit_fiat = self._fiat_converter.convert_amount( + total_profit_abs, + self._freqtrade.config['stake_currency'], + self._freqtrade.config['fiat_display_currency'] + ) # Calculate guaranteed profit (in case of trailing stop) stoploss_entry_dist = trade.calc_profit(trade.stop_loss) @@ -210,14 +225,14 @@ class RPC: trade_dict.update(dict( close_profit=trade.close_profit if not trade.is_open else None, current_rate=current_rate, - current_profit=current_profit, # Deprecated - current_profit_pct=round(current_profit * 100, 2), # Deprecated - current_profit_abs=current_profit_abs, # Deprecated profit_ratio=current_profit, profit_pct=round(current_profit * 100, 2), profit_abs=current_profit_abs, profit_fiat=current_profit_fiat, + total_profit_abs=total_profit_abs, + total_profit_fiat=total_profit_fiat, + total_profit_ratio=total_profit_ratio, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), @@ -327,11 +342,13 @@ class RPC: for day in range(0, timescale): profitday = start_date - time_offset(day) # Only query for necessary columns for performance reasons. - trades = Trade.query.session.query(Trade.close_profit_abs).filter( - Trade.is_open.is_(False), - Trade.close_date >= profitday, - Trade.close_date < (profitday + time_offset(1)) - ).order_by(Trade.close_date).all() + trades = Trade.session.execute( + select(Trade.close_profit_abs) + .filter(Trade.is_open.is_(False), + Trade.close_date >= profitday, + Trade.close_date < (profitday + time_offset(1))) + .order_by(Trade.close_date) + ).all() curdayprofit = sum( trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None) @@ -367,21 +384,27 @@ class RPC: def _rpc_trade_history(self, limit: int, offset: int = 0, order_by_id: bool = False) -> Dict: """ Returns the X last trades """ - order_by = Trade.id if order_by_id else Trade.close_date.desc() + order_by: Any = Trade.id if order_by_id else Trade.close_date.desc() if limit: - trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( - order_by).limit(limit).offset(offset) + trades = Trade.session.scalars( + Trade.get_trades_query([Trade.is_open.is_(False)]) + .order_by(order_by) + .limit(limit) + .offset(offset)) else: - trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( - Trade.close_date.desc()).all() + trades = Trade.session.scalars( + Trade.get_trades_query([Trade.is_open.is_(False)]) + .order_by(Trade.close_date.desc())) output = [trade.to_json() for trade in trades] + total_trades = Trade.session.scalar( + select(func.count(Trade.id)).filter(Trade.is_open.is_(False))) return { "trades": output, "trades_count": len(output), "offset": offset, - "total_trades": Trade.get_trades([Trade.is_open.is_(False)]).count(), + "total_trades": total_trades, } def _rpc_stats(self) -> Dict[str, Any]: @@ -395,7 +418,7 @@ class RPC: return 'losses' else: return 'draws' - trades: List[Trade] = Trade.get_trades([Trade.is_open.is_(False)], include_orders=False) + trades = Trade.get_trades([Trade.is_open.is_(False)], include_orders=False) # Sell reason exit_reasons = {} for trade in trades: @@ -404,7 +427,7 @@ class RPC: exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1 # Duration - dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []} + dur: Dict[str, List[float]] = {'wins': [], 'draws': [], 'losses': []} for trade in trades: if trade.close_date is not None and trade.open_date is not None: trade_dur = (trade.close_date - trade.open_date).total_seconds() @@ -423,8 +446,8 @@ class RPC: """ Returns cumulative profit statistics """ trade_filter = ((Trade.is_open.is_(False) & (Trade.close_date >= start_date)) | Trade.is_open.is_(True)) - trades: List[Trade] = Trade.get_trades( - trade_filter, include_orders=False).order_by(Trade.id).all() + trades: Sequence[Trade] = Trade.session.scalars(Trade.get_trades_query( + trade_filter, include_orders=False).order_by(Trade.id)).all() profit_all_coin = [] profit_all_ratio = [] @@ -443,11 +466,11 @@ class RPC: durations.append((trade.close_date - trade.open_date).total_seconds()) if not trade.is_open: - profit_ratio = trade.close_profit - profit_abs = trade.close_profit_abs + profit_ratio = trade.close_profit or 0.0 + profit_abs = trade.close_profit_abs or 0.0 profit_closed_coin.append(profit_abs) profit_closed_ratio.append(profit_ratio) - if trade.close_profit >= 0: + if profit_ratio >= 0: winning_trades += 1 winning_profit += profit_abs else: @@ -500,7 +523,7 @@ class RPC: trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'profit_abs': trade.close_profit_abs} - for trade in trades if not trade.is_open]) + for trade in trades if not trade.is_open and trade.close_date]) max_drawdown_abs = 0.0 max_drawdown = 0.0 if len(trades_df) > 0: @@ -779,7 +802,8 @@ class RPC: # check if valid pair # check if pair already has an open pair - trade: Trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() + trade: Optional[Trade] = Trade.get_trades( + [Trade.is_open.is_(True), Trade.pair == pair]).first() is_short = (order_side == SignalDirection.SHORT) if trade: is_short = trade.is_short @@ -932,12 +956,12 @@ class RPC: def _rpc_delete_lock(self, lockid: Optional[int] = None, pair: Optional[str] = None) -> Dict[str, Any]: """ Delete specific lock(s) """ - locks = [] + locks: Sequence[PairLock] = [] if pair: locks = PairLocks.get_pair_locks(pair) if lockid: - locks = PairLock.query.filter(PairLock.id == lockid).all() + locks = PairLock.session.scalars(select(PairLock).filter(PairLock.id == lockid)).all() for lock in locks: lock.active = False @@ -1198,10 +1222,23 @@ class RPC: "ram_pct": psutil.virtual_memory().percent } - def _health(self) -> Dict[str, Union[str, int]]: + def health(self) -> Dict[str, Optional[Union[str, int]]]: last_p = self._freqtrade.last_process + if last_p is None: + return { + "last_process": None, + "last_process_loc": None, + "last_process_ts": None, + } + return { - 'last_process': str(last_p), - 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), - 'last_process_ts': int(last_p.timestamp()), + "last_process": str(last_p), + "last_process_loc": last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), + "last_process_ts": int(last_p.timestamp()), } + + def _update_market_direction(self, direction: MarketDirection) -> None: + self._freqtrade.strategy.market_direction = direction + + def _get_market_direction(self) -> MarketDirection: + return self._freqtrade.strategy.market_direction diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index c4d4fa2dd..1972ad6e5 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -3,11 +3,12 @@ This module contains class to manage RPC communications (Telegram, API, ...) """ import logging from collections import deque -from typing import Any, Dict, List +from typing import List from freqtrade.constants import Config from freqtrade.enums import NO_ECHO_MESSAGES, RPCMessageType from freqtrade.rpc import RPC, RPCHandler +from freqtrade.rpc.rpc_types import RPCSendMsg logger = logging.getLogger(__name__) @@ -58,7 +59,7 @@ class RPCManager: mod.cleanup() del mod - def send_msg(self, msg: Dict[str, Any]) -> None: + def send_msg(self, msg: RPCSendMsg) -> None: """ Send given message to all registered rpc modules. A message consists of one or more key value pairs of strings. @@ -69,10 +70,6 @@ class RPCManager: """ if msg.get('type') not in NO_ECHO_MESSAGES: logger.info('Sending rpc message: %s', msg) - if 'pair' in msg: - msg.update({ - 'base_currency': self._rpc._freqtrade.exchange.get_pair_base_currency(msg['pair']) - }) for mod in self.registered_modules: logger.debug('Forwarding message to rpc.%s', mod.name) try: diff --git a/freqtrade/rpc/rpc_types.py b/freqtrade/rpc/rpc_types.py new file mode 100644 index 000000000..3277a2d6e --- /dev/null +++ b/freqtrade/rpc/rpc_types.py @@ -0,0 +1,128 @@ +from datetime import datetime +from typing import Any, List, Literal, Optional, TypedDict, Union + +from freqtrade.constants import PairWithTimeframe +from freqtrade.enums import RPCMessageType + + +class RPCSendMsgBase(TypedDict): + pass + # ty1pe: Literal[RPCMessageType] + + +class RPCStatusMsg(RPCSendMsgBase): + """Used for Status, Startup and Warning messages""" + type: Literal[RPCMessageType.STATUS, RPCMessageType.STARTUP, RPCMessageType.WARNING] + status: str + + +class RPCStrategyMsg(RPCSendMsgBase): + """Used for Status, Startup and Warning messages""" + type: Literal[RPCMessageType.STRATEGY_MSG] + msg: str + + +class RPCProtectionMsg(RPCSendMsgBase): + type: Literal[RPCMessageType.PROTECTION_TRIGGER, RPCMessageType.PROTECTION_TRIGGER_GLOBAL] + id: int + pair: str + base_currency: Optional[str] + lock_time: str + lock_timestamp: int + lock_end_time: str + lock_end_timestamp: int + reason: str + side: str + active: bool + + +class RPCWhitelistMsg(RPCSendMsgBase): + type: Literal[RPCMessageType.WHITELIST] + data: List[str] + + +class __RPCBuyMsgBase(RPCSendMsgBase): + trade_id: int + buy_tag: Optional[str] + enter_tag: Optional[str] + exchange: str + pair: str + base_currency: str + leverage: Optional[float] + direction: str + limit: float + open_rate: float + order_type: Optional[str] # TODO: why optional?? + stake_amount: float + stake_currency: str + fiat_currency: Optional[str] + amount: float + open_date: datetime + current_rate: Optional[float] + sub_trade: bool + + +class RPCBuyMsg(__RPCBuyMsgBase): + type: Literal[RPCMessageType.ENTRY, RPCMessageType.ENTRY_FILL] + + +class RPCCancelMsg(__RPCBuyMsgBase): + type: Literal[RPCMessageType.ENTRY_CANCEL] + reason: str + + +class RPCSellMsg(__RPCBuyMsgBase): + type: Literal[RPCMessageType.EXIT, RPCMessageType.EXIT_FILL] + cumulative_profit: float + gain: str # Literal["profit", "loss"] + close_rate: float + profit_amount: float + profit_ratio: float + sell_reason: Optional[str] + exit_reason: Optional[str] + close_date: datetime + # current_rate: Optional[float] + order_rate: Optional[float] + + +class RPCSellCancelMsg(__RPCBuyMsgBase): + type: Literal[RPCMessageType.EXIT_CANCEL] + reason: str + gain: str # Literal["profit", "loss"] + profit_amount: float + profit_ratio: float + sell_reason: Optional[str] + exit_reason: Optional[str] + close_date: datetime + + +class _AnalyzedDFData(TypedDict): + key: PairWithTimeframe + df: Any + la: datetime + + +class RPCAnalyzedDFMsg(RPCSendMsgBase): + """New Analyzed dataframe message""" + type: Literal[RPCMessageType.ANALYZED_DF] + data: _AnalyzedDFData + + +class RPCNewCandleMsg(RPCSendMsgBase): + """New candle ping message, issued once per new candle/pair""" + type: Literal[RPCMessageType.NEW_CANDLE] + data: PairWithTimeframe + + +RPCSendMsg = Union[ + RPCStatusMsg, + RPCStrategyMsg, + RPCProtectionMsg, + RPCWhitelistMsg, + RPCBuyMsg, + RPCCancelMsg, + RPCSellMsg, + RPCSellCancelMsg, + RPCAnalyzedDFMsg, + RPCNewCandleMsg + ] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fbd675d02..d79d8ea76 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -25,11 +25,12 @@ from telegram.utils.helpers import escape_markdown from freqtrade.__init__ import __version__ from freqtrade.constants import DUST_PER_COIN, Config -from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode +from freqtrade.enums import MarketDirection, RPCMessageType, SignalDirection, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException, RPCHandler +from freqtrade.rpc.rpc_types import RPCSendMsg logger = logging.getLogger(__name__) @@ -83,6 +84,8 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: self._send_msg(str(e)) except BaseException: logger.exception('Exception occurred within Telegram module') + finally: + Trade.session.remove() return wrapper @@ -129,7 +132,8 @@ class Telegram(RPCHandler): r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/forceexit$', - r'/edge$', r'/health$', r'/help$', r'/version$' + r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir (long|short|even|none)$', + r'/marketdir$' ] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -197,6 +201,7 @@ class Telegram(RPCHandler): CommandHandler('health', self._health), CommandHandler('help', self._help), CommandHandler('version', self._version), + CommandHandler('marketdir', self._changemarketdir) ] callbacks = [ CallbackQueryHandler(self._status_table, pattern='update_status_table'), @@ -319,31 +324,33 @@ class Telegram(RPCHandler): and self._rpc._fiat_converter): msg['profit_fiat'] = self._rpc._fiat_converter.convert_amount( msg['profit_amount'], msg['stake_currency'], msg['fiat_currency']) - msg['profit_extra'] = ( - f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']}") + msg['profit_extra'] = f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']}" else: msg['profit_extra'] = '' msg['profit_extra'] = ( f" ({msg['gain']}: {msg['profit_amount']:.8f} {msg['stake_currency']}" f"{msg['profit_extra']})") + is_fill = msg['type'] == RPCMessageType.EXIT_FILL is_sub_trade = msg.get('sub_trade') is_sub_profit = msg['profit_amount'] != msg.get('cumulative_profit') - profit_prefix = ('Sub ' if is_sub_profit - else 'Cumulative ') if is_sub_trade else '' + profit_prefix = ('Sub ' if is_sub_profit else 'Cumulative ') if is_sub_trade else '' cp_extra = '' + exit_wording = 'Exited' if is_fill else 'Exiting' if is_sub_profit and is_sub_trade: if self._rpc._fiat_converter: cp_fiat = self._rpc._fiat_converter.convert_amount( msg['cumulative_profit'], msg['stake_currency'], msg['fiat_currency']) cp_extra = f" / {cp_fiat:.3f} {msg['fiat_currency']}" - else: - cp_extra = '' - cp_extra = f"*Cumulative Profit:* (`{msg['cumulative_profit']:.8f} " \ - f"{msg['stake_currency']}{cp_extra}`)\n" + exit_wording = f"Partially {exit_wording.lower()}" + cp_extra = ( + f"*Cumulative Profit:* (`{msg['cumulative_profit']:.8f} " + f"{msg['stake_currency']}{cp_extra}`)\n" + ) + message = ( f"{msg['emoji']} *{self._exchange_from_msg(msg)}:* " - f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n" + f"{exit_wording} {msg['pair']} (#{msg['trade_id']})\n" f"{self._add_analyzed_candle(msg['pair'])}" f"*{f'{profit_prefix}Profit' if is_fill else f'Unrealized {profit_prefix}Profit'}:* " f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n" @@ -362,7 +369,7 @@ class Telegram(RPCHandler): elif msg['type'] == RPCMessageType.EXIT_FILL: message += f"*Exit Rate:* `{msg['close_rate']:.8f}`" - if msg.get('sub_trade'): + if is_sub_trade: if self._rpc._fiat_converter: msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount( msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) @@ -410,6 +417,9 @@ class Telegram(RPCHandler): elif msg_type == RPCMessageType.WARNING: message = f"\N{WARNING SIGN} *Warning:* `{msg['status']}`" + elif msg_type == RPCMessageType.EXCEPTION: + # Errors will contain exceptions, which are wrapped in tripple ticks. + message = f"\N{WARNING SIGN} *ERROR:* \n {msg['status']}" elif msg_type == RPCMessageType.STARTUP: message = f"{msg['status']}" @@ -420,14 +430,14 @@ class Telegram(RPCHandler): return None return message - def send_msg(self, msg: Dict[str, Any]) -> None: + def send_msg(self, msg: RPCSendMsg) -> None: """ Send a message to telegram channel """ default_noti = 'on' msg_type = msg['type'] noti = '' - if msg_type == RPCMessageType.EXIT: + if msg['type'] == RPCMessageType.EXIT: sell_noti = self._config['telegram'] \ .get('notification_settings', {}).get(str(msg_type), {}) # For backward compatibility sell still can be string @@ -444,7 +454,7 @@ class Telegram(RPCHandler): # Notification disabled return - message = self.compose_message(deepcopy(msg), msg_type) + message = self.compose_message(deepcopy(msg), msg_type) # type: ignore if message: self._send_msg(message, disable_notification=(noti == 'silent')) @@ -469,44 +479,51 @@ class Telegram(RPCHandler): lines_detail: List[str] = [] if len(filled_orders) > 0: first_avg = filled_orders[0]["safe_price"] - - for x, order in enumerate(filled_orders): + order_nr = 0 + for order in filled_orders: lines: List[str] = [] if order['is_open'] is True: continue + order_nr += 1 wording = 'Entry' if order['ft_is_entry'] else 'Exit' cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["filled"] or order["amount"] cur_entry_average = order["safe_price"] lines.append(" ") - if x == 0: - lines.append(f"*{wording} #{x+1}:*") + if order_nr == 1: + lines.append(f"*{wording} #{order_nr}:*") lines.append( - f"*Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") + f"*Amount:* {cur_entry_amount} " + f"({round_coin_value(order['cost'], quote_currency)})" + ) lines.append(f"*Average Price:* {cur_entry_average}") else: - sumA = 0 - sumB = 0 - for y in range(x): - amount = filled_orders[y]["filled"] or filled_orders[y]["amount"] - sumA += amount * filled_orders[y]["safe_price"] - sumB += amount - prev_avg_price = sumA / sumB + sum_stake = 0 + sum_amount = 0 + for y in range(order_nr): + loc_order = filled_orders[y] + if loc_order['is_open'] is True: + # Skip open orders (e.g. stop orders) + continue + amount = loc_order["filled"] or loc_order["amount"] + sum_stake += amount * loc_order["safe_price"] + sum_amount += amount + prev_avg_price = sum_stake / sum_amount # TODO: This calculation ignores fees. price_to_1st_entry = ((cur_entry_average - first_avg) / first_avg) minus_on_entry = 0 if prev_avg_price: minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price - lines.append(f"*{wording} #{x+1}:* at {minus_on_entry:.2%} avg profit") + lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg Profit") if is_open: lines.append("({})".format(cur_entry_datetime .humanize(granularity=["day", "hour", "minute"]))) - lines.append( - f"*Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") + lines.append(f"*Amount:* {cur_entry_amount} " + f"({round_coin_value(order['cost'], quote_currency)})") lines.append(f"*Average {wording} Price:* {cur_entry_average} " - f"({price_to_1st_entry:.2%} from 1st entry rate)") + f"({price_to_1st_entry:.2%} from 1st entry Rate)") lines.append(f"*Order filled:* {order['order_filled_date']}") # TODO: is this really useful? @@ -518,6 +535,7 @@ class Telegram(RPCHandler): # lines.append( # f"({days}d {hours}h {minutes}m {seconds}s from previous {wording.lower()})") lines_detail.append("\n".join(lines)) + return lines_detail @authorized_only @@ -553,35 +571,54 @@ class Telegram(RPCHandler): for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) + r['num_exits'] = len([o for o in r['orders'] if not o['ft_is_entry'] + and not o['ft_order_side'] == 'stoploss']) r['exit_reason'] = r.get('exit_reason', "") + r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) + r['max_stake_amount_r'] = round_coin_value( + r['max_stake_amount'] or r['stake_amount'], r['quote_currency']) + r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) + r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency']) + r['total_profit_abs_r'] = round_coin_value( + r['total_profit_abs'], r['quote_currency']) lines = [ "*Trade ID:* `{trade_id}`" + (" `(since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", - "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), - "*Leverage:* `{leverage}`" if r.get('leverage') else "", - "*Amount:* `{amount} ({stake_amount} {quote_currency})`", + f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" + + " ` ({leverage}x)`" if r.get('leverage') else "", + "*Amount:* `{amount} ({stake_amount_r})`", + "*Total invested:* `{max_stake_amount_r}`" if position_adjust else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", ] if position_adjust: max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "") - lines.append("*Number of Entries:* `{num_entries}`" + max_buy_str) + lines.extend([ + "*Number of Entries:* `{num_entries}" + max_buy_str + "`", + "*Number of Exits:* `{num_exits}`" + ]) lines.extend([ "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "", "*Open Date:* `{open_date}`", "*Close Date:* `{close_date}`" if r['close_date'] else "", - "*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", - ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") - + "`{profit_ratio:.2%}`", + " \n*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", + ("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *") + + "`{profit_ratio:.2%}` `({profit_abs_r})`", ]) if r['is_open']: if r.get('realized_profit'): - lines.append("*Realized Profit:* `{realized_profit:.8f}`") + lines.extend([ + "*Realized Profit:* `{realized_profit_ratio:.2%} ({realized_profit_r})`", + "*Total Profit:* `{total_profit_ratio:.2%} ({total_profit_abs_r})`" + ]) + + # Append empty line to improve readability + lines.append(" ") if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_ratio'] is not None): # Adding initial stoploss only if it is different from stoploss @@ -1040,10 +1077,14 @@ class Telegram(RPCHandler): query.answer() query.edit_message_text(text="Force exit canceled.") return - trade: Trade = Trade.get_trades(trade_filter=Trade.id == trade_id).first() + trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first() query.answer() - query.edit_message_text(text=f"Manually exiting Trade #{trade_id}, {trade.pair}") - self._force_exit_action(trade_id) + if trade: + query.edit_message_text( + text=f"Manually exiting Trade #{trade_id}, {trade.pair}") + self._force_exit_action(trade_id) + else: + query.edit_message_text(text=f"Trade {trade_id} not found.") def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': @@ -1302,7 +1343,7 @@ class Telegram(RPCHandler): message = tabulate({k: [v] for k, v in counts.items()}, headers=['current', 'max', 'total stake'], tablefmt='simple') - message = "
{}
".format(message) + message = f"
{message}
" logger.debug(message) self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True, callback_path="update_count", @@ -1494,6 +1535,9 @@ class Telegram(RPCHandler): "*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" + "*/marketdir [long | short | even | none]:* `Updates the user managed variable " + "that represents the current market direction. If no direction is provided `" + "`the currently set market direction will be output.` \n" "_Statistics_\n" "------------\n" @@ -1527,7 +1571,7 @@ class Telegram(RPCHandler): Handler for /health Shows the last process timestamp """ - health = self._rpc._health() + health = self._rpc.health() message = f"Last process: `{health['last_process_loc']}`" self._send_msg(message) @@ -1601,7 +1645,7 @@ class Telegram(RPCHandler): ]) else: reply_markup = InlineKeyboardMarkup([[]]) - msg += "\nUpdated: {}".format(datetime.now().ctime()) + msg += f"\nUpdated: {datetime.now().ctime()}" if not query.message: return chat_id = query.message.chat_id @@ -1677,3 +1721,39 @@ class Telegram(RPCHandler): 'TelegramError: %s! Giving up on that message.', telegram_err.message ) + + @authorized_only + def _changemarketdir(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /marketdir. + Updates the bot's market_direction + :param bot: telegram bot + :param update: message update + :return: None + """ + if context.args and len(context.args) == 1: + new_market_dir_arg = context.args[0] + old_market_dir = self._rpc._get_market_direction() + new_market_dir = None + if new_market_dir_arg == "long": + new_market_dir = MarketDirection.LONG + elif new_market_dir_arg == "short": + new_market_dir = MarketDirection.SHORT + elif new_market_dir_arg == "even": + new_market_dir = MarketDirection.EVEN + elif new_market_dir_arg == "none": + new_market_dir = MarketDirection.NONE + + if new_market_dir is not None: + self._rpc._update_market_direction(new_market_dir) + self._send_msg("Successfully updated market direction" + f" from *{old_market_dir}* to *{new_market_dir}*.") + else: + raise RPCException("Invalid market direction provided. \n" + "Valid market directions: *long, short, even, none*") + elif context.args is not None and len(context.args) == 0: + old_market_dir = self._rpc._get_market_direction() + self._send_msg(f"Currently set market direction: *{old_market_dir}*") + else: + raise RPCException("Invalid usage of command /marketdir. \n" + "Usage: */marketdir [short | long | even | none]*") diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index d81d8d24f..14b881126 100644 --- a/freqtrade/rpc/webhook.py +++ b/freqtrade/rpc/webhook.py @@ -10,6 +10,7 @@ from requests import RequestException, post from freqtrade.constants import Config from freqtrade.enums import RPCMessageType from freqtrade.rpc import RPC, RPCHandler +from freqtrade.rpc.rpc_types import RPCSendMsg logger = logging.getLogger(__name__) @@ -41,7 +42,7 @@ class Webhook(RPCHandler): """ pass - def _get_value_dict(self, msg: Dict[str, Any]) -> Optional[Dict[str, Any]]: + def _get_value_dict(self, msg: RPCSendMsg) -> Optional[Dict[str, Any]]: whconfig = self._config['webhook'] # Deprecated 2022.10 - only keep generic method. if msg['type'] in [RPCMessageType.ENTRY]: @@ -58,6 +59,7 @@ class Webhook(RPCHandler): valuedict = whconfig.get('webhookexitcancel') elif msg['type'] in (RPCMessageType.STATUS, RPCMessageType.STARTUP, + RPCMessageType.EXCEPTION, RPCMessageType.WARNING): valuedict = whconfig.get('webhookstatus') elif msg['type'].value in whconfig: @@ -74,7 +76,7 @@ class Webhook(RPCHandler): return None return valuedict - def send_msg(self, msg: Dict[str, Any]) -> None: + def send_msg(self, msg: RPCSendMsg) -> None: """ Send a message to telegram channel """ try: @@ -112,7 +114,7 @@ class Webhook(RPCHandler): response = post(self._url, data=payload['data'], headers={'Content-Type': 'text/plain'}) else: - raise NotImplementedError('Unknown format: {}'.format(self._format)) + raise NotImplementedError(f'Unknown format: {self._format}') # Throw a RequestException if the post was not successful response.raise_for_status() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1f687c196..6d4a3036f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,8 +12,8 @@ from pandas import DataFrame from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, - SignalTagType, SignalType, TradingMode) +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode, + SignalDirection, SignalTagType, SignalType, TradingMode) from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.misc import remove_entry_exit_signals @@ -122,6 +122,9 @@ class IStrategy(ABC, HyperStrategyMixin): # Definition of plot_config. See plotting documentation for more details. plot_config: Dict = {} + # A self set parameter that represents the market direction. filled from configuration + market_direction: MarketDirection = MarketDirection.NONE + def __init__(self, config: Config) -> None: self.config = config # Dict to determine if analysis is necessary @@ -248,11 +251,12 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def bot_loop_start(self, **kwargs) -> None: + def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). Might be used to perform pair-independent tasks (e.g. gather some remote resource for comparison) + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ pass diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index aa753a829..27ebe7e69 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -86,37 +86,41 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, def stoploss_from_open( open_relative_stop: float, current_profit: float, - is_short: bool = False + is_short: bool = False, + leverage: float = 1.0 ) -> float: """ - - Given the current profit, and a desired stop loss value relative to the open price, + Given the current profit, and a desired stop loss value relative to the trade entry price, return a stop loss value that is relative to the current price, and which can be returned from `custom_stoploss`. The requested stop can be positive for a stop above the open price, or negative for a stop below the open price. The return value is always >= 0. + `open_relative_stop` will be considered as adjusted for leverage if leverage is provided.. Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price - :param open_relative_stop: Desired stop loss percentage relative to open price + :param open_relative_stop: Desired stop loss percentage, relative to the open price, + adjusted for leverage :param current_profit: The current profit percentage :param is_short: When true, perform the calculation for short instead of long + :param leverage: Leverage to use for the calculation :return: Stop loss value relative to current price """ # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value - if (current_profit == -1 and not is_short) or (is_short and current_profit == 1): + _current_profit = current_profit / leverage + if (_current_profit == -1 and not is_short) or (is_short and _current_profit == 1): return 1 if is_short is True: - stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit)) + stoploss = -1 + ((1 - open_relative_stop / leverage) / (1 - _current_profit)) else: - stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit)) + stoploss = 1 - ((1 + open_relative_stop / leverage) / (1 + _current_profit)) # negative stoploss values indicate the requested stop price is higher/lower # (long/short) than the current price - return max(stoploss, 0.0) + return max(stoploss * leverage, 0.0) def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float: diff --git a/freqtrade/strategy/strategyupdater.py b/freqtrade/strategy/strategyupdater.py new file mode 100644 index 000000000..2669dcc4a --- /dev/null +++ b/freqtrade/strategy/strategyupdater.py @@ -0,0 +1,255 @@ +import shutil +from pathlib import Path + +import ast_comments + +from freqtrade.constants import Config + + +class StrategyUpdater: + name_mapping = { + 'ticker_interval': 'timeframe', + 'buy': 'enter_long', + 'sell': 'exit_long', + 'buy_tag': 'enter_tag', + 'sell_reason': 'exit_reason', + + 'sell_signal': 'exit_signal', + 'custom_sell': 'custom_exit', + 'force_sell': 'force_exit', + 'emergency_sell': 'emergency_exit', + + # Strategy/config settings: + 'use_sell_signal': 'use_exit_signal', + 'sell_profit_only': 'exit_profit_only', + 'sell_profit_offset': 'exit_profit_offset', + 'ignore_roi_if_buy_signal': 'ignore_roi_if_entry_signal', + 'forcebuy_enable': 'force_entry_enable', + } + + function_mapping = { + 'populate_buy_trend': 'populate_entry_trend', + 'populate_sell_trend': 'populate_exit_trend', + 'custom_sell': 'custom_exit', + 'check_buy_timeout': 'check_entry_timeout', + 'check_sell_timeout': 'check_exit_timeout', + # '': '', + } + # order_time_in_force, order_types, unfilledtimeout + otif_ot_unfilledtimeout = { + 'buy': 'entry', + 'sell': 'exit', + } + + # create a dictionary that maps the old column names to the new ones + rename_dict = {'buy': 'enter_long', 'sell': 'exit_long', 'buy_tag': 'enter_tag'} + + def start(self, config: Config, strategy_obj: dict) -> None: + """ + Run strategy updater + It updates a strategy to v3 with the help of the ast-module + :return: None + """ + + source_file = strategy_obj['location'] + strategies_backup_folder = Path.joinpath(config['user_data_dir'], "strategies_orig_updater") + target_file = Path.joinpath(strategies_backup_folder, strategy_obj['location_rel']) + + # read the file + with Path(source_file).open('r') as f: + old_code = f.read() + if not strategies_backup_folder.is_dir(): + Path(strategies_backup_folder).mkdir(parents=True, exist_ok=True) + + # backup original + # => currently no date after the filename, + # could get overridden pretty fast if this is fired twice! + # The folder is always the same and the file name too (currently). + shutil.copy(source_file, target_file) + + # update the code + new_code = self.update_code(old_code) + # write the modified code to the destination folder + with Path(source_file).open('w') as f: + f.write(new_code) + + # define the function to update the code + def update_code(self, code): + # parse the code into an AST + tree = ast_comments.parse(code) + + # use the AST to update the code + updated_code = self.modify_ast(tree) + + # return the modified code without executing it + return updated_code + + # function that uses the ast module to update the code + def modify_ast(self, tree): # noqa + # use the visitor to update the names and functions in the AST + NameUpdater().visit(tree) + + # first fix the comments, so it understands "\n" properly inside multi line comments. + ast_comments.fix_missing_locations(tree) + ast_comments.increment_lineno(tree, n=1) + + # generate the new code from the updated AST + # without indent {} parameters would just be written straight one after the other. + + # ast_comments would be amazing since this is the only solution that carries over comments, + # but it does currently not have an unparse function, hopefully in the future ... ! + # return ast_comments.unparse(tree) + + return ast_comments.unparse(tree) + + +# Here we go through each respective node, slice, elt, key ... to replace outdated entries. +class NameUpdater(ast_comments.NodeTransformer): + def generic_visit(self, node): + + # space is not yet transferred from buy/sell to entry/exit and thereby has to be skipped. + if isinstance(node, ast_comments.keyword): + if node.arg == "space": + return node + + # from here on this is the original function. + for field, old_value in ast_comments.iter_fields(node): + if isinstance(old_value, list): + new_values = [] + for value in old_value: + if isinstance(value, ast_comments.AST): + value = self.visit(value) + if value is None: + continue + elif not isinstance(value, ast_comments.AST): + new_values.extend(value) + continue + new_values.append(value) + old_value[:] = new_values + elif isinstance(old_value, ast_comments.AST): + new_node = self.visit(old_value) + if new_node is None: + delattr(node, field) + else: + setattr(node, field, new_node) + return node + + def visit_Expr(self, node): + if hasattr(node.value, "left") and hasattr(node.value.left, "id"): + node.value.left.id = self.check_dict(StrategyUpdater.name_mapping, node.value.left.id) + self.visit(node.value) + return node + + # Renames an element if contained inside a dictionary. + @staticmethod + def check_dict(current_dict: dict, element: str): + if element in current_dict: + element = current_dict[element] + return element + + def visit_arguments(self, node): + if isinstance(node.args, list): + for arg in node.args: + arg.arg = self.check_dict(StrategyUpdater.name_mapping, arg.arg) + return node + + def visit_Name(self, node): + # if the name is in the mapping, update it + node.id = self.check_dict(StrategyUpdater.name_mapping, node.id) + return node + + def visit_Import(self, node): + # do not update the names in import statements + return node + + def visit_ImportFrom(self, node): + # if hasattr(node, "module"): + # if node.module == "freqtrade.strategy.hyper": + # node.module = "freqtrade.strategy" + return node + + def visit_If(self, node: ast_comments.If): + for child in ast_comments.iter_child_nodes(node): + self.visit(child) + return node + + def visit_FunctionDef(self, node): + node.name = self.check_dict(StrategyUpdater.function_mapping, node.name) + self.generic_visit(node) + return node + + def visit_Attribute(self, node): + if ( + isinstance(node.value, ast_comments.Name) + and node.value.id == 'trade' + and node.attr == 'nr_of_successful_buys' + ): + node.attr = 'nr_of_successful_entries' + return node + + def visit_ClassDef(self, node): + # check if the class is derived from IStrategy + if any(isinstance(base, ast_comments.Name) and + base.id == 'IStrategy' for base in node.bases): + # check if the INTERFACE_VERSION variable exists + has_interface_version = any( + isinstance(child, ast_comments.Assign) and + isinstance(child.targets[0], ast_comments.Name) and + child.targets[0].id == 'INTERFACE_VERSION' + for child in node.body + ) + + # if the INTERFACE_VERSION variable does not exist, add it as the first child + if not has_interface_version: + node.body.insert(0, ast_comments.parse('INTERFACE_VERSION = 3').body[0]) + # otherwise, update its value to 3 + else: + for child in node.body: + if ( + isinstance(child, ast_comments.Assign) + and isinstance(child.targets[0], ast_comments.Name) + and child.targets[0].id == 'INTERFACE_VERSION' + ): + child.value = ast_comments.parse('3').body[0].value + self.generic_visit(node) + return node + + def visit_Subscript(self, node): + if isinstance(node.slice, ast_comments.Constant): + if node.slice.value in StrategyUpdater.rename_dict: + # Replace the slice attributes with the values from rename_dict + node.slice.value = StrategyUpdater.rename_dict[node.slice.value] + if hasattr(node.slice, "elts"): + self.visit_elts(node.slice.elts) + if hasattr(node.slice, "value"): + if hasattr(node.slice.value, "elts"): + self.visit_elts(node.slice.value.elts) + return node + + # elts can have elts (technically recursively) + def visit_elts(self, elts): + if isinstance(elts, list): + for elt in elts: + self.visit_elt(elt) + else: + self.visit_elt(elts) + return elts + + # sub function again needed since the structure itself is highly flexible ... + def visit_elt(self, elt): + if isinstance(elt, ast_comments.Constant) and elt.value in StrategyUpdater.rename_dict: + elt.value = StrategyUpdater.rename_dict[elt.value] + if hasattr(elt, "elts"): + self.visit_elts(elt.elts) + if hasattr(elt, "args"): + if isinstance(elt.args, ast_comments.arguments): + self.visit_elts(elt.args) + else: + for arg in elt.args: + self.visit_elts(arg) + return elt + + def visit_Constant(self, node): + node.value = self.check_dict(StrategyUpdater.otif_ot_unfilledtimeout, node.value) + node.value = self.check_dict(StrategyUpdater.name_mapping, node.value) + return node diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 index 488ca2fd7..bfbb20ec1 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 @@ -1,5 +1,5 @@ -def bot_loop_start(self, **kwargs) -> None: +def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). Might be used to perform pair-independent tasks @@ -8,6 +8,7 @@ def bot_loop_start(self, **kwargs) -> None: For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ When not implemented by a strategy, this simply does nothing. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ pass diff --git a/freqtrade/util/binance_mig.py b/freqtrade/util/binance_mig.py index 708bb1db7..37a2d2ef1 100644 --- a/freqtrade/util/binance_mig.py +++ b/freqtrade/util/binance_mig.py @@ -1,6 +1,7 @@ import logging from packaging import version +from sqlalchemy import select from freqtrade.constants import Config from freqtrade.enums.tradingmode import TradingMode @@ -44,7 +45,7 @@ def _migrate_binance_futures_db(config: Config): # Should symbol be migrated too? # order.symbol = new_pair Trade.commit() - pls = PairLock.query.filter(PairLock.pair.notlike('%:%')) + pls = PairLock.session.scalars(select(PairLock).filter(PairLock.pair.notlike('%:%'))).all() for pl in pls: pl.pair = f"{pl.pair}:{config['stake_currency']}" # print(pls) diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py index 3da4f038d..63797d462 100644 --- a/freqtrade/vendor/qtpylib/indicators.py +++ b/freqtrade/vendor/qtpylib/indicators.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # QTPyLib: Quantitative Trading Python Library # https://github.com/ranaroussi/qtpylib # @@ -18,7 +16,6 @@ # limitations under the License. # -import sys import warnings from datetime import datetime, timedelta @@ -27,11 +24,6 @@ import pandas as pd from pandas.core.base import PandasObject -# ============================================= -# check min, python version -if sys.version_info < (3, 4): - raise SystemError("QTPyLib requires Python version >= 3.4") - # ============================================= warnings.simplefilter(action="ignore", category=RuntimeWarning) diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 388163678..fb89e7a2d 100644 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -12,7 +12,7 @@ import sdnotify from freqtrade import __version__ from freqtrade.configuration import Configuration from freqtrade.constants import PROCESS_THROTTLE_SECS, RETRY_TIMEOUT, Config -from freqtrade.enums import State +from freqtrade.enums import RPCMessageType, State from freqtrade.exceptions import OperationalException, TemporaryError from freqtrade.exchange import timeframe_to_next_date from freqtrade.freqtradebot import FreqtradeBot @@ -185,7 +185,10 @@ class Worker: tb = traceback.format_exc() hint = 'Issue `/start` if you think it is safe to restart.' - self.freqtrade.notify_status(f'OperationalException:\n```\n{tb}```{hint}') + self.freqtrade.notify_status( + f'*OperationalException:*\n```\n{tb}```\n {hint}', + msg_type=RPCMessageType.EXCEPTION + ) logger.exception('OperationalException. Stopping trader ...') self.freqtrade.state = State.STOPPED diff --git a/pyproject.toml b/pyproject.toml index 82d4ceaf8..baf707c68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools >= 46.4.0", "wheel"] +build-backend = "setuptools.build_meta" + [tool.black] line-length = 100 exclude = ''' @@ -35,6 +39,9 @@ warn_unused_ignores = true exclude = [ '^build_helpers\.py$' ] +plugins = [ + "sqlalchemy.ext.mypy.plugin" +] [[tool.mypy.overrides]] module = "tests.*" @@ -45,10 +52,6 @@ ignore_errors = true module = "telegram.*" implicit_optional = true -[build-system] -requires = ["setuptools >= 46.4.0", "wheel"] -build-backend = "setuptools.build_meta" - [tool.pyright] include = ["freqtrade"] exclude = [ @@ -56,3 +59,28 @@ exclude = [ "build_helpers/*.py", ] ignore = ["freqtrade/vendor/**"] + + +[tool.ruff] +line-length = 100 +extend-exclude = [".env"] +target-version = "py38" +extend-select = [ + "C90", # mccabe + # "N", # pep8-naming + "UP", # pyupgrade + "TID", # flake8-tidy-imports + # "EXE", # flake8-executable + "YTT", # flake8-2020 + # "S", # flake8-bandit + # "DTZ", # flake8-datetimez + # "RSE", # flake8-raise + # "TCH", # flake8-type-checking + "PTH", # flake8-use-pathlib +] + +[tool.ruff.mccabe] +max-complexity = 12 + +[tool.ruff.per-file-ignores] +"tests/*" = ["S"] diff --git a/requirements-dev.txt b/requirements-dev.txt index 32b7cfcc5..3324c11e9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,12 +7,11 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -flake8==6.0.0 -flake8-tidy-imports==4.8.0 -mypy==1.0.1 -pre-commit==3.0.4 -pytest==7.2.1 -pytest-asyncio==0.20.3 +ruff==0.0.259 +mypy==1.1.1 +pre-commit==3.2.1 +pytest==7.2.2 +pytest-asyncio==0.21.0 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-random-order==1.1.0 @@ -23,11 +22,11 @@ time-machine==2.9.0 httpx==0.23.3 # Convert jupyter notebooks to markdown documents -nbconvert==7.2.9 +nbconvert==7.2.10 # mypy types -types-cachetools==5.3.0.0 +types-cachetools==5.3.0.4 types-filelock==3.2.7 -types-requests==2.28.11.13 -types-tabulate==0.9.0.0 -types-python-dateutil==2.8.19.6 +types-requests==2.28.11.16 +types-tabulate==0.9.0.1 +types-python-dateutil==2.8.19.10 diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index c242af43e..4de7d8fab 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -2,9 +2,9 @@ -r requirements-freqai.txt # Required for freqai-rl -torch==1.13.1 -stable-baselines3==1.7.0 -sb3-contrib==1.7.0 +torch==1.13.1; python_version < '3.11' +stable-baselines3==1.7.0; python_version < '3.11' +sb3-contrib==1.7.0; python_version < '3.11' # Gym is forced to this version by stable-baselines3. setuptools==65.5.1 # Should be removed when gym is fixed. -gym==0.21 +gym==0.21; python_version < '3.11' diff --git a/requirements-freqai.txt b/requirements-freqai.txt index cf5bc4c0b..e6eae667c 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -5,7 +5,7 @@ # Required for freqai scikit-learn==1.1.3 joblib==1.2.0 -catboost==1.1.1; platform_machine != 'aarch64' +catboost==1.1.1; platform_machine != 'aarch64' and 'arm' not in platform_machine and python_version < '3.11' lightgbm==3.3.5 -xgboost==1.7.3 +xgboost==1.7.4 tensorboard==2.12.0 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 904b5d661..2c7c27d98 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,5 +5,5 @@ scipy==1.10.1 scikit-learn==1.1.3 scikit-optimize==0.9.0 -filelock==3.9.0 +filelock==3.10.6 progressbar2==4.2.0 diff --git a/requirements-plot.txt b/requirements-plot.txt index b97d42fb6..ad7bade95 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.13.0 +plotly==5.13.1 diff --git a/requirements.txt b/requirements.txt index 855aa664d..b888d9f6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,15 +2,15 @@ numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.8.17 -cryptography==39.0.1 +ccxt==3.0.37 +cryptography==40.0.1 aiohttp==3.8.4 -SQLAlchemy==1.4.46 +SQLAlchemy==2.0.7 python-telegram-bot==13.15 arrow==1.2.3 cachetools==4.2.2 requests==2.28.2 -urllib3==1.26.14 +urllib3==1.26.15 jsonschema==4.17.3 TA-Lib==0.4.25 technical==1.4.0 @@ -26,17 +26,17 @@ pyarrow==11.0.0; platform_machine != 'armv7l' py_find_1st==1.1.5 # Load ticker files 30% faster -python-rapidjson==1.9 +python-rapidjson==1.10 # Properly format api responses -orjson==3.8.6 +orjson==3.8.8 # Notify systemd sdnotify==0.3.2 # API Server -fastapi==0.92.0 -pydantic==1.10.4 -uvicorn==0.20.0 +fastapi==0.95.0 +pydantic==1.10.7 +uvicorn==0.21.1 pyjwt==2.6.0 aiofiles==23.1.0 psutil==5.9.4 @@ -45,7 +45,7 @@ psutil==5.9.4 colorama==0.4.6 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.36 +prompt-toolkit==3.0.38 # Extensions to datetime library python-dateutil==2.8.2 @@ -55,3 +55,5 @@ schedule==1.1.0 #WS Messages websockets==10.4 janus==1.0.0 + +ast-comments==1.0.1 diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 144d428e5..196542780 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -340,11 +340,13 @@ class FtRestClient(): :param limit: Limit result to the last n candles. :return: json object """ - return self._get("pair_candles", params={ + params = { "pair": pair, "timeframe": timeframe, - "limit": limit, - }) + } + if limit: + params['limit'] = limit + return self._get("pair_candles", params=params) def pair_history(self, pair, timeframe, strategy, timerange=None): """Return historic, analyzed dataframe diff --git a/setup.cfg b/setup.cfg index 60ec8a75f..b54b62619 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Operating System :: MacOS Operating System :: Unix Topic :: Office/Business :: Financial :: Investment diff --git a/setup.py b/setup.py index 30aacc3f2..edd7b243b 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,6 @@ hdf5 = [ develop = [ 'coveralls', - 'flake8', - 'flake8-tidy-imports', 'mypy', 'pytest', 'pytest-asyncio', diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 55ffaccb0..318590b32 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -14,7 +14,8 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star start_hyperopt_show, start_install_ui, start_list_data, start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, start_new_strategy, start_show_trades, - start_test_pairlist, start_trading, start_webserver) + start_strategy_update, start_test_pairlist, start_trading, + start_webserver) from freqtrade.commands.db_commands import start_convert_db from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui, get_ui_download_url, read_ui_version) @@ -24,7 +25,7 @@ from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.persistence.models import init_db from freqtrade.persistence.pairlock_middleware import PairLocks -from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) from tests.conftest_trades import MOCK_TRADE_COUNT @@ -454,7 +455,7 @@ def test_list_markets(mocker, markets_static, capsys): assert re.search(r"^BLK/BTC$", captured.out, re.MULTILINE) assert re.search(r"^LTC/USD$", captured.out, re.MULTILINE) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.markets', PropertyMock(side_effect=ValueError)) # Test --one-column args = [ "list-markets", @@ -643,9 +644,7 @@ def test_download_data_keyboardInterrupt(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", "binance", @@ -664,9 +663,7 @@ def test_download_data_timerange(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", "binance", @@ -715,9 +712,7 @@ def test_download_data_no_markets(mocker, caplog): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker, id='binance') - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "binance", @@ -733,9 +728,7 @@ def test_download_data_no_exchange(mocker, caplog): mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", ] @@ -751,9 +744,7 @@ def test_download_data_no_pairs(mocker): mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", @@ -771,9 +762,7 @@ def test_download_data_all_pairs(mocker, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) args = [ "download-data", "--exchange", @@ -810,9 +799,7 @@ def test_download_data_trades(mocker, caplog): convert_mock = mocker.patch('freqtrade.commands.data_commands.convert_trades_to_ohlcv', MagicMock(return_value=[])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "kraken", @@ -843,9 +830,7 @@ def test_download_data_trades(mocker, caplog): def test_download_data_data_invalid(mocker): patch_exchange(mocker, id="kraken") - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "download-data", "--exchange", "kraken", @@ -862,9 +847,7 @@ def test_start_convert_trades(mocker, caplog): convert_mock = mocker.patch('freqtrade.commands.data_commands.convert_trades_to_ohlcv', MagicMock(return_value=[])) patch_exchange(mocker) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) args = [ "trades-to-ohlcv", "--exchange", "kraken", @@ -971,7 +954,7 @@ def test_start_list_freqAI_models(capsys): def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): patch_exchange(mocker, mock_markets=True) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), get_tickers=tickers, ) @@ -1564,3 +1547,37 @@ def test_start_convert_db(mocker, fee, tmpdir, caplog): start_convert_db(pargs) assert db_target_file.is_file() + + +def test_start_strategy_updater(mocker, tmpdir): + sc_mock = mocker.patch('freqtrade.commands.strategy_utils_commands.start_conversion') + teststrats = Path(__file__).parent.parent / 'strategy/strats' + args = [ + "strategy-updater", + "--userdir", + str(tmpdir), + "--strategy-path", + str(teststrats), + ] + pargs = get_args(args) + pargs['config'] = None + start_strategy_update(pargs) + # Number of strategies in the test directory + assert sc_mock.call_count == 11 + + sc_mock.reset_mock() + args = [ + "strategy-updater", + "--userdir", + str(tmpdir), + "--strategy-path", + str(teststrats), + "--strategy-list", + "StrategyTestV3", + "StrategyTestV2" + ] + pargs = get_args(args) + pargs['config'] = None + start_strategy_update(pargs) + # Number of strategies in the test directory + assert sc_mock.call_count == 2 diff --git a/tests/conftest.py b/tests/conftest.py index c74b1f0f1..0aa6e70a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,6 +40,7 @@ np.seterr(all='raise') CURRENT_TEST_STRATEGY = 'StrategyTestV3' TRADE_SIDES = ('long', 'short') +EXMS = 'freqtrade.exchange.exchange.Exchange' def pytest_addoption(parser): @@ -145,22 +146,21 @@ def patch_exchange( mock_markets=True, mock_supported_modes=True ) -> None: - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + mocker.patch(f'{EXMS}._load_async_markets', return_value={}) + mocker.patch(f'{EXMS}.validate_config', MagicMock()) + mocker.patch(f'{EXMS}.validate_timeframes', MagicMock()) + mocker.patch(f'{EXMS}.id', PropertyMock(return_value=id)) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value=id.title())) + mocker.patch(f'{EXMS}.precisionMode', PropertyMock(return_value=2)) if mock_markets: if isinstance(mock_markets, bool): mock_markets = get_markets() - mocker.patch('freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=mock_markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=mock_markets)) if mock_supported_modes: mocker.patch( - f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_margin_pairs', + f'freqtrade.exchange.{id}.{id.capitalize()}._supported_trading_mode_margin_pairs', PropertyMock(return_value=[ (TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.MARGIN, MarginMode.ISOLATED), @@ -170,10 +170,10 @@ def patch_exchange( ) if api_mock: - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', return_value=api_mock) else: - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( + mocker.patch(f'{EXMS}._init_ccxt', MagicMock()) + mocker.patch(f'{EXMS}.timeframes', PropertyMock( return_value=['5m', '15m', '1h', '1d'])) @@ -299,7 +299,7 @@ def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = Tru """ def add_trade(trade): if use_db: - Trade.query.session.add(trade) + Trade.session.add(trade) else: LocalTrade.add_bt_trade(trade) is_short1 = is_short if is_short is not None else True @@ -332,11 +332,11 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True): Create some fake trades ... """ if use_db: - Trade.query.session.rollback() + Trade.session.rollback() def add_trade(trade): if use_db: - Trade.query.session.add(trade) + Trade.session.add(trade) else: LocalTrade.add_bt_trade(trade) @@ -366,7 +366,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True): add_trade(trade) if use_db: - Trade.query.session.flush() + Trade.session.flush() def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True): @@ -375,7 +375,7 @@ def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool """ def add_trade(trade): if use_db: - Trade.query.session.add(trade) + Trade.session.add(trade) else: LocalTrade.add_bt_trade(trade) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 345e3c299..2c5515f7c 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -98,7 +98,7 @@ def test_load_backtest_data_new_format(testdatadir): assert bt_data.equals(bt_data3) with pytest.raises(ValueError, match=r"File .* does not exist\."): - load_backtest_data(str("filename") + "nofile") + load_backtest_data("filename" + "nofile") with pytest.raises(ValueError, match=r"Unknown dataformat."): load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN) diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index f673ede6e..f19b15455 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -252,7 +252,7 @@ def test_datahandler__check_empty_df(testdatadir, caplog): assert log_has_re(expected_text, caplog) -@pytest.mark.parametrize('datahandler', ['feather', 'parquet']) +@pytest.mark.parametrize('datahandler', ['parquet']) def test_datahandler_trades_not_supported(datahandler, testdatadir, ): dh = get_datahandler(testdatadir, datahandler) with pytest.raises(NotImplementedError): @@ -496,6 +496,58 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir): assert unlinkmock.call_count == 2 +def test_featherdatahandler_trades_load(testdatadir): + dh = get_datahandler(testdatadir, 'feather') + trades = dh.trades_load('XRP/ETH') + assert isinstance(trades, list) + assert trades[0][0] == 1570752011620 + assert trades[-1][-1] == 0.1986231 + + trades1 = dh.trades_load('UNITTEST/NONEXIST') + assert trades1 == [] + + +def test_featherdatahandler_trades_store(testdatadir, tmpdir): + tmpdir1 = Path(tmpdir) + dh = get_datahandler(testdatadir, 'feather') + trades = dh.trades_load('XRP/ETH') + + dh1 = get_datahandler(tmpdir1, 'feather') + dh1.trades_store('XRP/NEW', trades) + file = tmpdir1 / 'XRP_NEW-trades.feather' + assert file.is_file() + # Load trades back + trades_new = dh1.trades_load('XRP/NEW') + + assert len(trades_new) == len(trades) + assert trades[0][0] == trades_new[0][0] + assert trades[0][1] == trades_new[0][1] + # assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense + assert trades[0][3] == trades_new[0][3] + assert trades[0][4] == trades_new[0][4] + assert trades[0][5] == trades_new[0][5] + assert trades[0][6] == trades_new[0][6] + assert trades[-1][0] == trades_new[-1][0] + assert trades[-1][1] == trades_new[-1][1] + # assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense + assert trades[-1][3] == trades_new[-1][3] + assert trades[-1][4] == trades_new[-1][4] + assert trades[-1][5] == trades_new[-1][5] + assert trades[-1][6] == trades_new[-1][6] + + +def test_featherdatahandler_trades_purge(mocker, testdatadir): + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + unlinkmock = mocker.patch.object(Path, "unlink", MagicMock()) + dh = get_datahandler(testdatadir, 'feather') + assert not dh.trades_purge('UNITTEST/NONEXIST') + assert unlinkmock.call_count == 0 + + mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + assert dh.trades_purge('UNITTEST/NONEXIST') + assert unlinkmock.call_count == 1 + + def test_gethandlerclass(): cl = get_datahandlerclass('json') assert cl == JsonDataHandler diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index c6b1dcc5a..0e10b5848 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -8,7 +8,7 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import generate_test_data, get_patched_exchange +from tests.conftest import EXMS, generate_test_data, get_patched_exchange @pytest.mark.parametrize('candle_type', [ @@ -223,7 +223,7 @@ def test_emit_df(mocker, default_conf, ohlcv_history): def test_refresh(mocker, default_conf): refresh_mock = MagicMock() - mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", refresh_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance") timeframe = default_conf["timeframe"] @@ -281,7 +281,7 @@ def test_market(mocker, default_conf, markets): def test_ticker(mocker, default_conf, tickers): ticker_mock = MagicMock(return_value=tickers()['ETH/BTC']) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('ETH/BTC') @@ -290,7 +290,7 @@ def test_ticker(mocker, default_conf, tickers): assert res['symbol'] == 'ETH/BTC' ticker_mock = MagicMock(side_effect=ExchangeError('Pair not found')) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('UNITTEST/BTC') @@ -301,7 +301,7 @@ def test_current_whitelist(mocker, default_conf, tickers): # patch default conf to volumepairlist default_conf['pairlists'][0] = {'method': 'VolumePairList', "number_assets": 5} - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), get_tickers=tickers) exchange = get_patched_exchange(mocker, default_conf) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 7d313c446..24ad8bcc9 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -26,7 +26,7 @@ from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import file_dump_json from freqtrade.resolvers import StrategyResolver -from tests.conftest import (CURRENT_TEST_STRATEGY, get_patched_exchange, log_has, log_has_re, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_patched_exchange, log_has, log_has_re, patch_exchange) @@ -66,7 +66,7 @@ def test_load_data_7min_timeframe(caplog, testdatadir) -> None: def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history) file = testdatadir / 'UNITTEST_BTC-1m.json' load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC']) assert file.is_file() @@ -77,7 +77,7 @@ def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history) file = testdatadir / 'futures/UNITTEST_USDT_USDT-1h-mark.json' load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark') assert file.is_file() @@ -109,7 +109,7 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, Test load_pair_history() with 1 min timeframe """ tmpdir1 = Path(tmpdir) - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) file = tmpdir1 / 'MEME_BTC-1m.json' @@ -191,7 +191,7 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: test_data = None test_filename = testdatadir.joinpath('UNITTEST_BTC-1m.json') - with open(test_filename, "rt") as file: + with test_filename.open("rt") as file: test_data = json.load(file) test_data_df = ohlcv_to_dataframe(test_data, '1m', 'UNITTEST/BTC', @@ -277,7 +277,7 @@ def test_download_pair_history( subdir, file_tail ) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) tmpdir1 = Path(tmpdir) file1_1 = tmpdir1 / f'{subdir}MEME_BTC-1m{file_tail}.json' @@ -328,7 +328,7 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: json_dump_mock = mocker.patch( 'freqtrade.data.history.jsondatahandler.JsonDataHandler.ohlcv_store', return_value=None) - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick) + mocker.patch(f'{EXMS}.get_historic_ohlcv', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", timeframe='1m', candle_type='spot') @@ -340,7 +340,7 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdir) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', + mocker.patch(f'{EXMS}.get_historic_ohlcv', side_effect=Exception('File Error')) tmpdir1 = Path(tmpdir) exchange = get_patched_exchange(mocker, default_conf) @@ -409,7 +409,7 @@ def test_init_with_refresh(default_conf, mocker) -> None: def test_file_dump_json_tofile(testdatadir) -> None: - file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4())) + file = testdatadir / f'test_{uuid.uuid4()}.json' data = {'bar': 'foo'} # check the file we will create does not exist @@ -506,9 +506,7 @@ def test_refresh_backtest_ohlcv_data( mocker, default_conf, markets, caplog, testdatadir, trademode, callcount): dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history', MagicMock()) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "unlink", MagicMock()) @@ -531,9 +529,7 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir): MagicMock()) ex = get_patched_exchange(mocker, default_conf) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) timerange = TimeRange.parse_timerange("20190101-20190102") unav_pairs = refresh_backtest_ohlcv_data(exchange=ex, pairs=["BTT/BTC", "LTC/USDT"], timeframes=["1m", "5m"], @@ -551,9 +547,7 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir): def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, testdatadir): dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_trades_history', MagicMock()) - mocker.patch( - 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) mocker.patch.object(Path, "unlink", MagicMock()) @@ -577,8 +571,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad tmpdir) -> None: tmpdir1 = Path(tmpdir) ght_mock = MagicMock(side_effect=lambda pair, *args, **kwargs: (pair, trades_history)) - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - ght_mock) + mocker.patch(f'{EXMS}.get_historic_trades', ght_mock) exchange = get_patched_exchange(mocker, default_conf) file1 = tmpdir1 / 'ETH_BTC-trades.json.gz' data_handler = get_datahandler(tmpdir1, data_format='jsongz') @@ -604,8 +597,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad file1.unlink() - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.get_historic_trades', MagicMock(side_effect=ValueError)) assert not _download_trades_history(data_handler=data_handler, exchange=exchange, pair='ETH/BTC') @@ -615,8 +607,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad copyfile(testdatadir / file2.name, file2) ght_mock.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', - ght_mock) + mocker.patch(f'{EXMS}.get_historic_trades', ght_mock) # Since before first start date since_time = int(trades_history[0][0] // 1000) - 500 timerange = TimeRange('date', None, since_time, 0) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index e414d7624..be0346b78 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -14,7 +14,7 @@ from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.enums import ExitType from freqtrade.exceptions import OperationalException -from tests.conftest import get_patched_freqtradebot, log_has +from tests.conftest import EXMS, get_patched_freqtradebot, log_has from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, _get_frame_time_from_offset) @@ -261,7 +261,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', def test_edge_process_downloaded_data(mocker, edge_conf): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) @@ -273,7 +273,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf): def test_edge_process_no_data(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock()) mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={})) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) @@ -286,7 +286,7 @@ def test_edge_process_no_data(mocker, edge_conf, caplog): def test_edge_process_no_trades(mocker, edge_conf, caplog): freqtrade = get_patched_freqtradebot(mocker, edge_conf) - mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001) + mocker.patch(f'{EXMS}.get_fee', return_value=0.001) mocker.patch('freqtrade.edge.edge_positioning.refresh_data', ) mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) # Return empty @@ -303,7 +303,7 @@ def test_edge_process_no_pairs(mocker, edge_conf, caplog): mocker.patch('freqtrade.freqtradebot.validate_config_consistency') freqtrade = get_patched_freqtradebot(mocker, edge_conf) - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001) + fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.001) mocker.patch('freqtrade.edge.edge_positioning.refresh_data') mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data) # Return empty @@ -319,7 +319,7 @@ def test_edge_process_no_pairs(mocker, edge_conf, caplog): def test_edge_init_error(mocker, edge_conf,): edge_conf['stake_amount'] = 0.5 - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.001)) with pytest.raises(OperationalException, match='Edge works only with unlimited stake amount'): get_patched_freqtradebot(mocker, edge_conf) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 79d3c0836..fda33b859 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -7,10 +7,23 @@ import pytest from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re +from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers +@pytest.mark.parametrize('side,type,time_in_force,expected', [ + ('buy', 'limit', 'gtc', {'timeInForce': 'GTC'}), + ('buy', 'limit', 'IOC', {'timeInForce': 'IOC'}), + ('buy', 'market', 'IOC', {}), + ('buy', 'limit', 'PO', {'timeInForce': 'PO'}), + ('sell', 'limit', 'PO', {'timeInForce': 'PO'}), + ('sell', 'market', 'PO', {}), + ]) +def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected): + exchange = get_patched_exchange(mocker, default_conf, id='binance') + assert exchange._get_params(side, type, 1, False, time_in_force) == expected + + @pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT]) @pytest.mark.parametrize('limitratio,expected,side', [ (None, 220 * 0.99, "sell"), @@ -34,12 +47,12 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte default_conf['dry_run'] = False default_conf['margin_mode'] = MarginMode.ISOLATED default_conf['trading_mode'] = trademode - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss( pair='ETH/BTC', amount=1, @@ -113,12 +126,12 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker): api_mock = MagicMock() order_type = 'stop_loss_limit' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss( pair='ETH/BTC', amount=1, @@ -542,7 +555,6 @@ def test__set_leverage_binance(mocker, default_conf): "set_leverage", pair="XRP/USDT", leverage=5.0, - trading_mode=TradingMode.FUTURES ) @@ -600,7 +612,7 @@ def test_get_maintenance_ratio_and_amt_binance( mm_ratio, amt, ): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange._leverage_tiers = leverage_tiers (result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value) diff --git a/tests/exchange/test_bitpanda.py b/tests/exchange/test_bitpanda.py index 4bd168e7e..de44be986 100644 --- a/tests/exchange/test_bitpanda.py +++ b/tests/exchange/test_bitpanda.py @@ -1,7 +1,7 @@ from datetime import datetime from unittest.mock import MagicMock -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange def test_get_trades_for_order(default_conf, mocker): @@ -9,7 +9,7 @@ def test_get_trades_for_order(default_conf, mocker): order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock = MagicMock() api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index 7c8324bf6..d0d5114a1 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from unittest.mock import MagicMock from freqtrade.enums.marginmode import MarginMode @@ -55,3 +56,19 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] assert kwargs['params'] == {'until': since_ms_end} assert kwargs['since'] == since_ms + + +def test_bybit_get_funding_fees(default_conf, mocker): + now = datetime.now(timezone.utc) + exchange = get_patched_exchange(mocker, default_conf, id='bybit') + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) + assert exchange._fetch_and_calculate_funding_fees.call_count == 0 + + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id='bybit') + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) + + assert exchange._fetch_and_calculate_funding_fees.call_count == 1 diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index bbeb56c6a..4a65b16d7 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -17,7 +17,7 @@ from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_default_conf_usdt +from tests.conftest import EXMS, get_default_conf_usdt EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] @@ -37,7 +37,7 @@ EXCHANGES = { 'stake_currency': 'USDT', 'use_ci_proxy': True, 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures': True, 'futures_pair': 'BTC/USDT:USDT', 'hasQuoteVolumeFutures': True, @@ -66,7 +66,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures': False, 'sample_order': [{ "symbol": "SOLUSDT", @@ -91,7 +91,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'leverage_tiers_public': False, 'leverage_in_spot_market': True, }, @@ -99,7 +99,7 @@ EXCHANGES = { 'pair': 'XRP/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'leverage_tiers_public': False, 'leverage_in_spot_market': True, 'sample_order': [ @@ -141,7 +141,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures': True, 'futures_pair': 'BTC/USDT:USDT', 'hasQuoteVolumeFutures': True, @@ -215,7 +215,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures': True, 'futures_pair': 'BTC/USDT:USDT', 'hasQuoteVolumeFutures': False, @@ -226,7 +226,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures_pair': 'BTC/USDT:USDT', 'futures': True, 'leverage_tiers_public': True, @@ -253,14 +253,14 @@ EXCHANGES = { 'pair': 'ETH/BTC', 'stake_currency': 'BTC', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'futures': False, }, 'bitvavo': { 'pair': 'BTC/EUR', 'stake_currency': 'EUR', 'hasQuoteVolume': True, - 'timeframe': '5m', + 'timeframe': '1h', 'leverage_tiers_public': False, 'leverage_in_spot_market': False, }, @@ -322,13 +322,12 @@ def exchange_futures(request, exchange_conf, class_mocker): class_mocker.patch( 'freqtrade.exchange.binance.Binance.fill_leverage_tiers') - class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') + class_mocker.patch(f'{EXMS}.fetch_trading_fees') class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') - class_mocker.patch('freqtrade.exchange.exchange.Exchange.load_cached_leverage_tiers', - return_value=None) - class_mocker.patch('freqtrade.exchange.exchange.Exchange.cache_leverage_tiers') + class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None) + class_mocker.patch(f'{EXMS}.cache_leverage_tiers') exchange = ExchangeResolver.load_exchange( request.param, exchange_conf, validate=True, load_leverage_tiers=True) @@ -464,7 +463,9 @@ class TestCCXTExchange(): if exchangename == 'gate': # TODO: Gate is unstable here at the moment, ignoring the limit partially. return - for val in [1, 2, 5, 25, 100]: + for val in [1, 2, 5, 25, 50, 100]: + if val > 50 and exchangename == 'bybit': + continue l2 = exch.fetch_l2_order_book(pair, val) if not l2_limit_range or val in l2_limit_range: if val > 50: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 13613df37..e08815e61 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -12,8 +12,8 @@ from pandas import DataFrame from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError, - InvalidOrderException, OperationalException, PricingError, - TemporaryError) + InsufficientFundsError, InvalidOrderException, + OperationalException, PricingError, TemporaryError) from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision, date_minus_candles, market_is_active, price_to_precision, timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, @@ -22,12 +22,12 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_CO calculate_backoff, remove_credentials) from freqtrade.exchange.exchange import amount_to_contract_precision from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_exchange, log_has, - log_has_re, num_log_has_re) +from tests.conftest import (EXMS, generate_test_data_raw, get_mock_coro, get_patched_exchange, + log_has, log_has_re, num_log_has_re) # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate', 'kucoin', 'bybit'] get_entry_rate_data = [ ('other', 20, 19, 10, 0.0, 20), # Full ask side @@ -113,18 +113,21 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu exchange = get_patched_exchange(mocker, default_conf, api_mock) await getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == retries + exchange.close() with pytest.raises(TemporaryError): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock) await getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == retries + exchange.close() with pytest.raises(OperationalException): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock) await getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 + exchange.close() def test_init(default_conf, mocker, caplog): @@ -150,9 +153,9 @@ def test_remove_credentials(default_conf, caplog) -> None: def test_init_ccxt_kwargs(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - aei_mock = mocker.patch('freqtrade.exchange.Exchange.additional_exchange_init') + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_stakecurrency') + aei_mock = mocker.patch(f'{EXMS}.additional_exchange_init') caplog.set_level(logging.INFO) conf = copy.deepcopy(default_conf) @@ -218,12 +221,12 @@ def test_init_exception(default_conf, mocker): def test_exchange_resolver(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=MagicMock())) + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pairs') + 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) assert isinstance(exchange, Exchange) @@ -362,9 +365,8 @@ def test_price_to_precision(price, precision_mode, precision, expected): def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected): markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', - PropertyMock(return_value=precision_mode)) + mocker.patch(f'{EXMS}.markets', markets) + mocker.patch(f'{EXMS}.precisionMode', PropertyMock(return_value=precision_mode)) pair = 'ETH/BTC' assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected @@ -376,10 +378,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} # no pair found - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) with pytest.raises(ValueError, match=r'.*get market information.*'): exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) @@ -388,10 +387,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': None, 'max': None}, 'amount': {'min': None, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) assert result is None result = exchange.get_max_pair_stake_amount('ETH/BTC', 1) @@ -402,10 +398,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 2, 'max': 10000}, 'amount': {'min': None, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) expected_result = 2 * (1 + 0.05) / (1 - abs(stoploss)) @@ -422,10 +415,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': None, 'max': None}, 'amount': {'min': 2, 'max': 10000}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -441,10 +431,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 2, 'max': None}, 'amount': {'min': 2, 'max': None}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -457,10 +444,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: 'cost': {'min': 8, 'max': 10000}, 'amount': {'min': 2, 'max': 500}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) expected_result = max(8, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result @@ -496,10 +480,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # Contract size 0.01 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) @@ -509,10 +490,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert result == 10 markets["ETH/BTC"]["contractSize"] = '10' - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) # With Leverage, Contract size 10 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert pytest.approx(result) == (expected_result / 12) * 10.0 @@ -531,10 +509,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: 'cost': {'min': 0.0001, 'max': 4000}, 'amount': {'min': 0.001, 'max': 10000}, } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) expected_result = max(0.0001, 0.001 * 0.020405) * (1 + 0.05) / (1 - abs(stoploss)) assert round(result, 8) == round(expected_result, 8) @@ -592,12 +567,12 @@ def test_set_sandbox_exception(default_conf, mocker): def test__load_async_markets(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._init_ccxt') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') exchange = Exchange(default_conf) exchange._api_async.load_markets = get_mock_coro(None) exchange._load_async_markets() @@ -614,19 +589,19 @@ def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError")) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) assert log_has('Unable to initialize markets.', caplog) expected_return = {'ETH/BTC': 'available'} api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=expected_return) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] ex = Exchange(default_conf) @@ -684,11 +659,11 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -699,17 +674,17 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog): 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'XRP is not available as stake on .*' 'Available currencies are: BTC, ETH, USDT'): Exchange(default_conf) type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.')) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) with pytest.raises(OperationalException, match=r'Could not load markets, therefore cannot start\. Please.*'): @@ -757,11 +732,11 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -770,10 +745,10 @@ def test_validate_pairs_not_available(default_conf, mocker): type(api_mock).markets = PropertyMock(return_value={ 'XRP/BTC': {'inactive': True, 'base': 'XRP', 'quote': 'BTC'} }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'not available'): Exchange(default_conf) @@ -782,19 +757,19 @@ def test_validate_pairs_not_available(default_conf, mocker): def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) type(api_mock).markets = PropertyMock(return_value={}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch(f'{EXMS}._init_ccxt', api_mock) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}._load_async_markets') with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): Exchange(default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) Exchange(default_conf) assert log_has('Unable to validate pairs (assuming they are correct).', caplog) @@ -806,11 +781,11 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC', 'info': {'prohibitedIn': ['US']}}, 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') Exchange(default_conf) assert log_has("Pair XRP/BTC is restricted for some users on this exchange." @@ -825,11 +800,11 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -842,11 +817,11 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) assert type(api_mock).load_markets.call_count == 1 @@ -860,10 +835,10 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'USDT'}, }) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): Exchange(default_conf) @@ -883,11 +858,11 @@ def test_validate_timeframes(default_conf, mocker, timeframe): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') Exchange(default_conf) @@ -903,9 +878,9 @@ def test_validate_timeframes_failed(default_conf, mocker): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) with pytest.raises(OperationalException, match=r"Invalid timeframe '3m'. This exchange supports.*"): Exchange(default_conf) @@ -925,10 +900,10 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): # delete timeframes so magicmock does not autocreate it del api_mock.timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' r'for the exchange .* and this exchange ' @@ -945,11 +920,11 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): # delete timeframes so magicmock does not autocreate it del api_mock.timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={'timeframes': None})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) + mocker.patch(f'{EXMS}.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' r'for the exchange .* and this exchange ' @@ -969,12 +944,12 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): '1h': '1h'}) type(api_mock).timeframes = timeframes - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_required_startup_candles') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_required_startup_candles') Exchange(default_conf) @@ -985,13 +960,13 @@ def test_validate_pricing(default_conf, mocker): 'fetchTicker': True, } type(api_mock).has = PropertyMock(return_value=has) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.name', 'Binance') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.name', 'Binance') ExchangeResolver.load_exchange('binance', default_conf) has.update({'fetchTicker': False}) with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): @@ -1020,13 +995,13 @@ def test_validate_ordertypes(default_conf, mocker): api_mock = MagicMock() type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.name', 'Bittrex') default_conf['order_types'] = { 'entry': 'limit', @@ -1037,7 +1012,7 @@ def test_validate_ordertypes(default_conf, mocker): Exchange(default_conf) type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) default_conf['order_types'] = { 'entry': 'limit', @@ -1067,9 +1042,9 @@ def test_validate_ordertypes(default_conf, mocker): ('bybit', 'last', True), ('bybit', 'mark', True), ('bybit', 'index', True), - # ('okx', 'last', True), - # ('okx', 'mark', True), - # ('okx', 'index', True), + ('okx', 'last', True), + ('okx', 'mark', True), + ('okx', 'index', True), ('gate', 'last', True), ('gate', 'mark', True), ('gate', 'index', True), @@ -1080,12 +1055,12 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') default_conf['order_types'] = { 'entry': 'limit', 'exit': 'limit', @@ -1103,12 +1078,12 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') conf = copy.deepcopy(default_conf) Exchange(conf) @@ -1116,14 +1091,14 @@ def test_validate_order_types_not_in_config(default_conf, mocker): def test_validate_required_startup_candles(default_conf, mocker, caplog): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch(f'{EXMS}._init_ccxt', api_mock) + mocker.patch(f'{EXMS}.validate_timeframes') + mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f'{EXMS}.validate_stakecurrency') default_conf['startup_candle_count'] = 20 ex = Exchange(default_conf) @@ -1220,11 +1195,10 @@ def test_create_dry_run_order_fees( fee, ): mocker.patch( - 'freqtrade.exchange.Exchange.get_fee', - side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 + f'{EXMS}.get_fee', + side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 ) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', - return_value=price_side == 'other') + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side == 'other') exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_dry_run_order( @@ -1241,8 +1215,7 @@ def test_create_dry_run_order_fees( else: assert order['fee'] is None - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', - return_value=price_side != 'other') + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side != 'other') order1 = exchange.fetch_dry_run_order(order['id']) assert order1['fee']['rate'] == fee @@ -1264,12 +1237,12 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill exchange_name, order_book_l2_usd, converted): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), fetch_l2_order_book=order_book_l2_usd, ) - order = exchange.create_dry_run_order( + order = exchange.create_order( pair='LTC/USDT', ordertype='limit', side=side, @@ -1303,8 +1276,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill order_book_l2_usd.reset_mock() # Empty orderbook test - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'asks': [], 'bids': []}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'asks': [], 'bids': []}) exchange._dry_run_open_orders[order['id']]['status'] = 'open' order_closed = exchange.fetch_dry_run_order(order['id']) @@ -1327,12 +1299,12 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou exchange_name, order_book_l2_usd): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, exchange_has=MagicMock(return_value=True), fetch_l2_order_book=order_book_l2_usd, ) - order = exchange.create_dry_run_order( + order = exchange.create_order( pair='LTC/USDT', ordertype='market', side=side, @@ -1372,8 +1344,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, }) default_conf['dry_run'] = False default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() @@ -1425,9 +1397,10 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, assert order['amount'] == 0.01 -def test_buy_dry_run(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_buy_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", amount=1, rate=200, leverage=1.0, @@ -1451,8 +1424,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", @@ -1466,7 +1439,10 @@ def test_buy_prod(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] is None + if exchange._order_needs_price(order_type): + assert api_mock.create_order.call_args[0][4] == 200 + else: + assert api_mock.create_order.call_args[0][4] is None api_mock.create_order.reset_mock() order_type = 'limit' @@ -1535,8 +1511,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' @@ -1571,7 +1547,10 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] is None + if exchange._order_needs_price(order_type): + assert api_mock.create_order.call_args[0][4] == 200 + else: + assert api_mock.create_order.call_args[0][4] is None # Market orders should not send timeInForce!! assert "timeInForce" not in api_mock.create_order.call_args[0][5] @@ -1601,8 +1580,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, @@ -1615,7 +1594,10 @@ def test_sell_prod(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'sell' assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] is None + if exchange._order_needs_price(order_type): + assert api_mock.create_order.call_args[0][4] == 200 + else: + assert api_mock.create_order.call_args[0][4] is None api_mock.create_order.reset_mock() order_type = 'limit' @@ -1629,13 +1611,13 @@ def test_sell_prod(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][4] == 200 # test exception handling - with pytest.raises(DependencyException): + with pytest.raises(InsufficientFundsError): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0) - with pytest.raises(DependencyException): + with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200, @@ -1674,8 +1656,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): }) api_mock.options = {} default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' @@ -1709,7 +1691,10 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'sell' assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] is None + if exchange._order_needs_price(order_type): + assert api_mock.create_order.call_args[0][4] == 200 + else: + assert api_mock.create_order.call_args[0][4] is None # Market orders should not send timeInForce!! assert "timeInForce" not in api_mock.create_order.call_args[0][5] @@ -1741,7 +1726,7 @@ def test_get_balances_prod(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_positions(default_conf, mocker, exchange_name): - mocker.patch('freqtrade.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') api_mock = MagicMock() api_mock.fetch_positions = MagicMock(return_value=[ {'symbol': 'ETH/USDT:USDT', 'leverage': 5}, @@ -1797,7 +1782,7 @@ def test_fetch_trading_fees(default_conf, mocker): default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED api_mock.fetch_trading_fees = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert '1INCH/USDT:USDT' in exchange._trading_fees @@ -1812,7 +1797,7 @@ def test_fetch_trading_fees(default_conf, mocker): api_mock.fetch_trading_fees = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_trading_fees() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) assert exchange.fetch_trading_fees() == {} @@ -1832,7 +1817,7 @@ def test_fetch_bids_asks(default_conf, mocker): } exchange_name = 'binance' api_mock.fetch_bids_asks = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker bidsasks = exchange.fetch_bids_asks() @@ -1865,7 +1850,7 @@ def test_fetch_bids_asks(default_conf, mocker): api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_bids_asks() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) assert exchange.fetch_bids_asks() == {} @@ -1884,7 +1869,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): 'last': 41, } } - mocker.patch('freqtrade.exchange.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock.fetch_tickers = MagicMock(return_value=tick) api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) @@ -1927,7 +1912,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): api_mock.fetch_bids_asks.reset_mock() default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() @@ -1936,7 +1921,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): api_mock.fetch_tickers.reset_mock() api_mock.fetch_bids_asks.reset_mock() - mocker.patch('freqtrade.exchange.exchange.Exchange.exchange_has', return_value=False) + mocker.patch(f'{EXMS}.exchange_has', return_value=False) assert exchange.get_tickers() == {} @@ -2190,7 +2175,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach time_machine.move_to(start + timedelta(hours=99, minutes=30)) exchange = get_patched_exchange(mocker, default_conf) - mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100) + mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) assert exchange._startup_candle_count == 0 exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) @@ -2215,7 +2200,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 assert exchange._klines - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 exchange._api_async.fetch_ohlcv.reset_mock() # Returned from cache @@ -2224,7 +2209,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res) == 2 assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 # Move time 1 candle further but result didn't change yet time_machine.move_to(start + timedelta(hours=101)) @@ -2234,13 +2219,13 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 assert res[pair2].at[0, 'open'] - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 refresh_pior = exchange._pairs_last_refresh_time[pair1] # New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles # in one go new_startdate = (start + timedelta(hours=2)).strftime('%Y-%m-%d %H:%M') - # mocker.patch("freqtrade.exchange.Exchange.ohlcv_candle_limit", return_value=100) + # mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) ohlcv = generate_test_data_raw('1h', 100, new_startdate) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) res = exchange.refresh_latest_ohlcv(pairs) @@ -2252,8 +2237,8 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert res[pair2].at[0, 'open'] assert refresh_pior != exchange._pairs_last_refresh_time[pair1] - assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 - assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 + assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-2][0] // 1000 exchange._api_async.fetch_ohlcv.reset_mock() # Retry same call - from cache @@ -2278,7 +2263,6 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert res[pair2].at[0, 'open'] -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): ohlcv = [ @@ -2307,7 +2291,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ assert res[3] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog) - + exchange.close() # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_get_candle_history", "fetch_ohlcv", @@ -2322,15 +2306,17 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, (arrow.utcnow().int_timestamp - 2000) * 1000) + exchange.close() + with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' r'historical candle \(OHLCV\) data\..*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, (arrow.utcnow().int_timestamp - 2000) * 1000) + exchange.close() -@pytest.mark.asyncio async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): from freqtrade.exchange.common import _reset_logging_mixin _reset_logging_mixin() @@ -2340,8 +2326,8 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" "429 Too Many Requests" '{"code":"429000","msg":"Too Many Requests"}')) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="KuCoin") - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='KuCoin')) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") + mocker.patch(f'{EXMS}.name', PropertyMock(return_value='KuCoin')) msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" assert not num_log_has_re(msg, caplog) @@ -2371,9 +2357,9 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): # Expect the "returned exception" message 12 times (4 retries * 3 (loop)) assert num_log_has_re(msg, caplog) == 12 assert num_log_has_re(msg2, caplog) == 9 + exchange.close() -@pytest.mark.asyncio async def test__async_get_candle_history_empty(default_conf, mocker, caplog): """ Test empty exchange result """ ohlcv = [] @@ -2393,6 +2379,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog): assert res[2] == CandleType.SPOT assert res[3] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 + exchange.close() def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): @@ -2499,8 +2486,7 @@ def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid, default_conf['entry_pricing']['price_last_balance'] = last_ab default_conf['entry_pricing']['price_side'] = side exchange = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected assert not log_has("Using cached entry rate for ETH/BTC.", caplog) @@ -2521,8 +2507,7 @@ def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask, default_conf['exit_pricing']['price_side'] = side if last_ab is not None: default_conf['exit_pricing']['price_last_balance'] = last_ab - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'bid': bid, 'last': last}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last}) pair = "ETH/BTC" # Test regular mode @@ -2555,8 +2540,7 @@ def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_sho default_conf['exit_pricing']['price_side'] = side default_conf['exit_pricing']['price_last_balance'] = last_ab exchange = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) with pytest.raises(PricingError): exchange.get_rate('ETH/BTC', refresh=True, side=entry, is_short=is_short) @@ -2580,7 +2564,7 @@ def test_get_exit_rate_orderbook( default_conf['exit_pricing']['use_order_book'] = True default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) exchange = get_patched_exchange(mocker, default_conf) rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) assert not log_has("Using cached exit rate for ETH/BTC.", caplog) @@ -2598,8 +2582,7 @@ def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog): default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" # Test What happens if the exchange returns an empty orderbook. - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'bids': [[]], 'asks': [[]]}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError): exchange.get_rate(pair, refresh=True, side="exit", is_short=False) @@ -2613,8 +2596,7 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short): # Ticker on one side can be empty in certain circumstances. default_conf['exit_pricing']['price_side'] = 'ask' pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': None, 'bid': 0.12, 'last': None}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': None, 'bid': 0.12, 'last': None}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) @@ -2622,8 +2604,7 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short): exchange._config['exit_pricing']['price_side'] = 'bid' assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12 # Reverse sides - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', - return_value={'ask': 0.13, 'bid': None, 'last': None}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': 0.13, 'bid': None, 'last': None}) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) @@ -2793,7 +2774,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na assert res_ohlcv[9][5] == 2.31452783 -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, fetch_trades_result): @@ -2821,8 +2801,8 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000 assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'} assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog) + exchange.close() - exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_fetch_trades", "fetch_trades", pair='ABCD/BTC', since=None) @@ -2832,15 +2812,16 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) + exchange.close() with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' r'historical trade data\..*'): api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) + exchange.close() -@pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name, fetch_trades_result): @@ -2875,6 +2856,7 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e pair = 'ETH/USDT:USDT' res = await exchange._async_fetch_trades(pair, since=None, params=None) assert res[0][5] == 300 + exchange.close() @pytest.mark.asyncio @@ -2989,7 +2971,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) pair = 'ETH/BTC' @@ -3011,7 +2993,7 @@ def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name, trades_history): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=False) + mocker.patch(f'{EXMS}.exchange_has', return_value=False) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) pair = 'ETH/BTC' @@ -3027,7 +3009,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} @@ -3155,33 +3137,33 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123}) - mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch('freqtrade.exchange.gate.Gate.fetch_stoploss_order', return_value={'for': 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) res = {'fee': {}, 'status': 'canceled', 'amount': 1234} - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res) - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value=res) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=res) + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', return_value=res) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == res - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled') - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value='canceled') + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value='canceled') + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', return_value='canceled') # Fall back to fetch_stoploss_order co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == {'for': 123} exc = InvalidOrderException("") - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', side_effect=exc) + mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.gate.Gate.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}} with pytest.raises(InvalidOrderException): exc = InvalidOrderException("Did not find order") - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', side_effect=exc) + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.gate.Gate.cancel_stoploss_order', side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) @@ -3274,7 +3256,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='binance') fetch_order_mock = MagicMock() fetch_stoploss_order_mock = MagicMock() - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, fetch_order=fetch_order_mock, fetch_stoploss_order=fetch_stoploss_order_mock, ) @@ -3314,7 +3296,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, default_conf["dry_run"] = False default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) api_mock = MagicMock() api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', @@ -3357,7 +3339,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, 'get_trades_for_order', 'fetch_my_trades', order_id=order_id, pair='ETH/USDT:USDT', since=since) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=False)) assert exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) == [] @@ -3403,7 +3385,7 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -3423,7 +3405,7 @@ def test_merge_ft_has_dict(default_conf, mocker): ex = Binance(default_conf) assert ex._ft_has != Exchange._ft_has_default assert ex.get_option('stoploss_on_exchange') - assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC'] + assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC', 'PO'] assert ex.get_option('trades_pagination') == 'id' assert ex.get_option('trades_pagination_arg') == 'fromId' @@ -3438,7 +3420,7 @@ def test_merge_ft_has_dict(default_conf, mocker): def test_get_valid_pair_combination(default_conf, mocker, markets): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -3530,7 +3512,7 @@ def test_get_markets(default_conf, mocker, markets_static, spot_only, futures_only, expected_keys, test_comment # Here for debugging purposes (Not used within method) ): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), @@ -3549,7 +3531,7 @@ def test_get_markets(default_conf, mocker, markets_static, def test_get_markets_error(default_conf, mocker): ex = get_patched_exchange(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=None)) with pytest.raises(OperationalException, match="Markets were not loaded."): ex.get_markets('LTC', 'USDT', True, False) @@ -3694,7 +3676,7 @@ def test_market_is_tradable( quote, spot, margin, futures, trademode, add_dict, exchange, expected_result ) -> None: default_conf['trading_mode'] = trademode - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') ex = get_patched_exchange(mocker, default_conf, id=exchange) market = { 'symbol': market_symbol, @@ -3739,7 +3721,7 @@ def test_order_has_fee(order, expected) -> None: (0.34, 'USDT', 0.01)), ]) def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: - mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01)) + mocker.patch(f'{EXMS}.calculate_fee_rate', MagicMock(return_value=0.01)) ex = get_patched_exchange(mocker, default_conf) assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected @@ -3784,7 +3766,7 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: 'fee': {'currency': None, 'cost': 0.005}}, None, None), ]) def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None: - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081}) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'last': 0.081}) if unknown_fee_rate: default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate @@ -3856,7 +3838,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): ]) type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) - # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y) + # mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') unix_time = int(date_time.timestamp()) @@ -3904,29 +3886,6 @@ def test_get_stake_amount_considering_leverage( stake_amount, leverage) == min_stake_with_lev -@pytest.mark.parametrize("exchange_name,trading_mode", [ - ("binance", TradingMode.FUTURES), -]) -def test__set_leverage(mocker, default_conf, exchange_name, trading_mode): - - api_mock = MagicMock() - api_mock.set_leverage = MagicMock() - type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) - default_conf['dry_run'] = False - - ccxt_exceptionhandlers( - mocker, - default_conf, - api_mock, - exchange_name, - "_set_leverage", - "set_leverage", - pair="XRP/USDT", - leverage=5.0, - trading_mode=trading_mode - ) - - @pytest.mark.parametrize("margin_mode", [ (MarginMode.CROSS), (MarginMode.ISOLATED) @@ -4256,8 +4215,7 @@ def test__fetch_and_calculate_funding_fees( type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( - return_value=['1h', '4h', '8h'])) + mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['1h', '4h', '8h'])) funding_fees = ex._fetch_and_calculate_funding_fees( pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2) assert pytest.approx(funding_fees) == expected_fees @@ -4267,7 +4225,7 @@ def test__fetch_and_calculate_funding_fees( assert pytest.approx(funding_fees) == -expected_fees # Return empty "refresh_latest" - mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", return_value={}) + mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value={}) ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) with pytest.raises(ExchangeError, match="Could not find funding rates."): ex._fetch_and_calculate_funding_fees( @@ -4293,7 +4251,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called( return_value=funding_rate_history_octohourly) type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) - mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock(return_value=['4h', '8h'])) + mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['4h', '8h'])) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z') @@ -4316,7 +4274,7 @@ def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_m default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', { + mocker.patch(f'{EXMS}.markets', { 'LTC/USD': { 'symbol': 'LTC/USD', 'contractSize': None, @@ -4352,7 +4310,7 @@ def test__order_contracts_to_amount( api_mock = MagicMock() default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) orders = [ @@ -4474,7 +4432,7 @@ def test__trades_contracts_to_amount( api_mock = MagicMock() default_conf['trading_mode'] = trading_mode default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) trades = [ @@ -4510,7 +4468,7 @@ def test__amount_to_contracts( default_conf['trading_mode'] = 'spot' default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', { + mocker.patch(f'{EXMS}.markets', { 'LTC/USD': { 'symbol': 'LTC/USD', 'contractSize': None, @@ -4768,7 +4726,7 @@ def test_get_max_pair_stake_amount( }, } - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0) == 20000 assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0, 5) == 4000 assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0) == float('inf') @@ -4778,7 +4736,7 @@ def test_get_max_pair_stake_amount( default_conf['trading_mode'] = 'spot' exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch(f'{EXMS}.markets', markets) assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0) == 20000 assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 @@ -4789,7 +4747,7 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name api_mock.fetch_leverage_tiers = MagicMock() type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') api_mock.fetch_leverage_tiers = MagicMock(return_value={ 'ADA/USDT:USDT': [ @@ -4867,7 +4825,6 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name ) -@pytest.mark.asyncio @pytest.mark.parametrize('exchange_name', EXCHANGES) async def test_get_market_leverage_tiers(mocker, default_conf, exchange_name): default_conf['exchange']['name'] = exchange_name @@ -4942,7 +4899,7 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage api_mock = MagicMock() default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers @@ -4979,7 +4936,7 @@ def test_get_maintenance_ratio_and_amt( api_mock = MagicMock() default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers exchange.get_maintenance_ratio_and_amt(pair, value) == (mmr, maintAmt) @@ -5015,10 +4972,10 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01) -@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx']) +@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx', 'bybit']) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange._params = {'test': True} @@ -5036,6 +4993,9 @@ def test__get_params(mocker, default_conf, exchange_name): params2['tdMode'] = 'isolated' params2['posSide'] = 'net' + if exchange_name == 'bybit': + params2['position_idx'] = 0 + assert exchange._get_params( side="buy", ordertype='market', @@ -5105,7 +5065,7 @@ def test_get_liquidation_price1(mocker, default_conf): ] api_mock.fetch_positions = MagicMock(return_value=positions) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) default_conf['dry_run'] = False @@ -5275,7 +5235,7 @@ def test_get_liquidation_price( default_conf_usdt['trading_mode'] = trading_mode default_conf_usdt['exchange']['name'] = exchange_name default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') + mocker.patch('freqtrade.exchange.gate.Gate.validate_ordertypes') exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) @@ -5320,8 +5280,8 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun 'symbol': 'ETH/BTC', }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_contract_size = MagicMock(return_value=contract_size) diff --git a/tests/exchange/test_gate.py b/tests/exchange/test_gate.py index f777dd7d0..3cb5a9a3e 100644 --- a/tests/exchange/test_gate.py +++ b/tests/exchange/test_gate.py @@ -4,40 +4,7 @@ from unittest.mock import MagicMock import pytest from freqtrade.enums import MarginMode, TradingMode -from freqtrade.exceptions import OperationalException -from freqtrade.exchange import Gate -from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_patched_exchange - - -def test_validate_order_types_gate(default_conf, mocker): - default_conf['exchange']['name'] = 'gate' - mocker.patch('freqtrade.exchange.Exchange._init_ccxt') - mocker.patch('freqtrade.exchange.Exchange._load_markets', return_value={}) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs') - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.name', 'Gate') - exch = ExchangeResolver.load_exchange('gate', default_conf, True) - assert isinstance(exch, Gate) - - default_conf['order_types'] = { - 'entry': 'market', - 'exit': 'limit', - 'stoploss': 'market', - 'stoploss_on_exchange': False - } - - with pytest.raises(OperationalException, - match=r'Exchange .* does not support market orders.'): - ExchangeResolver.load_exchange('gate', default_conf, True) - - # market-orders supported on futures markets. - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' - ex = ExchangeResolver.load_exchange('gate', default_conf, True) - assert ex +from tests.conftest import EXMS, get_patched_exchange @pytest.mark.usefixtures("init_persistence") @@ -105,7 +72,7 @@ def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side): ('maker', 0.0, 0.0), ]) def test_fetch_my_trades_gate(mocker, default_conf, takerormaker, rate, cost): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) tick = {'ETH/USDT:USDT': { 'info': {'user_id': '', 'taker_fee': '0.0018', diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py index e5fa986c3..85d2ced9d 100644 --- a/tests/exchange/test_huobi.py +++ b/tests/exchange/test_huobi.py @@ -4,8 +4,8 @@ from unittest.mock import MagicMock import ccxt import pytest -from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_patched_exchange +from freqtrade.exceptions import DependencyException, InvalidOrderException +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -26,12 +26,12 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={'stoploss_on_exchange_limit_ratio': 1.05}, side=side, @@ -79,12 +79,12 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker): api_mock = MagicMock() order_type = 'stop-limit' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={'stoploss_on_exchange_limit_ratio': 1.05}, side='sell', leverage=1.0) diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 3a183de93..40a5a5b38 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -5,7 +5,7 @@ import ccxt import pytest from freqtrade.exceptions import DependencyException, InvalidOrderException -from tests.conftest import get_patched_exchange +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -28,8 +28,8 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order( @@ -68,8 +68,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, @@ -191,8 +191,8 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') @@ -262,8 +262,8 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index 65c855b7a..07f3fb6a3 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -4,8 +4,8 @@ from unittest.mock import MagicMock import ccxt import pytest -from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException -from tests.conftest import get_patched_exchange +from freqtrade.exceptions import DependencyException, InvalidOrderException +from tests.conftest import EXMS, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -26,12 +26,12 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') if order_type == 'limit': - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={ 'stoploss': order_type, @@ -87,12 +87,12 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker): api_mock = MagicMock() order_type = 'market' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') - with pytest.raises(OperationalException): + with pytest.raises(InvalidOrderException): order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={'stoploss': 'limit', 'stoploss_on_exchange_limit_ratio': 1.05}, @@ -125,3 +125,45 @@ def test_stoploss_adjust_kucoin(mocker, default_conf): # Test with invalid order case order['stopPrice'] = None assert exchange.stoploss_adjust(1501, order, 'sell') + + +@pytest.mark.parametrize("side", ["buy", "sell"]) +@pytest.mark.parametrize("ordertype,rate", [ + ("market", None), + ("market", 200), + ("limit", 200), + ("stop_loss_limit", 200) +]) +def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate): + api_mock = MagicMock() + order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + }, + 'symbol': 'XRP/USDT', + 'amount': 1 + }) + default_conf['dry_run'] = False + mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='kucoin') + exchange._set_leverage = MagicMock() + exchange.set_margin_mode = MagicMock() + + order = exchange.create_order( + pair='XRP/USDT', + ordertype=ordertype, + side=side, + amount=1, + rate=rate, + leverage=1.0 + ) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert order['amount'] == 1 + # Status must be faked to open for kucoin. + assert order['status'] == 'open' diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index 46b1852a0..7a3fa22f0 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -2,11 +2,13 @@ from datetime import datetime, timedelta, timezone from pathlib import Path from unittest.mock import MagicMock, PropertyMock +import ccxt import pytest from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.exceptions import RetryableOrderError from freqtrade.exchange.exchange import timeframe_to_minutes -from tests.conftest import get_mock_coro, get_patched_exchange, log_has +from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -46,7 +48,7 @@ def test_get_maintenance_ratio_and_amt_okx( default_conf['margin_mode'] = 'isolated' default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Okx', + 'freqtrade.exchange.okx.Okx', exchange_has=MagicMock(return_value=True), load_leverage_tiers=MagicMock(return_value={ 'ETH/USDT:USDT': [ @@ -476,3 +478,116 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog, exchange.load_leverage_tiers() assert log_has(logmsg, caplog) + + +def test__set_leverage_okx(mocker, default_conf): + + api_mock = MagicMock() + api_mock.set_leverage = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange._lev_prep('BTC/USDT:USDT', 3.2, 'buy') + assert api_mock.set_leverage.call_count == 1 + # Leverage is rounded to 3. + assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3.2 + assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT' + assert api_mock.set_leverage.call_args_list[0][1]['params'] == { + 'mgnMode': 'isolated', + 'posSide': 'net'} + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "okx", + "_lev_prep", + "set_leverage", + pair="XRP/USDT:USDT", + leverage=5.0, + side='buy' + ) + + +@pytest.mark.usefixtures("init_persistence") +def test_fetch_stoploss_order_okx(default_conf, mocker): + default_conf['dry_run'] = False + api_mock = MagicMock() + api_mock.fetch_order = MagicMock() + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx') + + exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert api_mock.fetch_order.call_count == 1 + assert api_mock.fetch_order.call_args_list[0][0][0] == '1234' + assert api_mock.fetch_order.call_args_list[0][0][1] == 'ETH/BTC' + assert api_mock.fetch_order.call_args_list[0][1]['params'] == {'stop': True} + + api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound) + api_mock.fetch_open_orders = MagicMock(return_value=[]) + api_mock.fetch_closed_orders = MagicMock(return_value=[]) + api_mock.fetch_canceled_orders = MagicMock(creturn_value=[]) + + with pytest.raises(RetryableOrderError): + exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert api_mock.fetch_order.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + assert api_mock.fetch_canceled_orders.call_count == 1 + + api_mock.fetch_order.reset_mock() + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + api_mock.fetch_canceled_orders.reset_mock() + + api_mock.fetch_closed_orders = MagicMock(return_value=[ + { + 'id': '1234', + 'status': 'closed', + 'info': {'ordId': '123455'} + } + ]) + mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value={'id': '123455'})) + resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert api_mock.fetch_order.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + assert api_mock.fetch_canceled_orders.call_count == 0 + + assert resp['id'] == '1234' + assert resp['id_stop'] == '123455' + assert resp['type'] == 'stoploss' + + default_conf['dry_run'] = True + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx') + dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={'id': '123455'})) + + api_mock.fetch_order.reset_mock() + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + api_mock.fetch_canceled_orders.reset_mock() + resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC') + + assert api_mock.fetch_order.call_count == 0 + assert api_mock.fetch_open_orders.call_count == 0 + assert api_mock.fetch_closed_orders.call_count == 0 + assert api_mock.fetch_canceled_orders.call_count == 0 + assert dro_mock.call_count == 1 + + +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side): + exchange = get_patched_exchange(mocker, default_conf, id='okx') + order = { + 'type': 'stoploss', + 'price': 1500, + 'stopLossPrice': 1500, + } + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index 68e7ea49a..e140ee80b 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -78,7 +78,9 @@ def make_rl_config(conf): "rr": 1, "profit_aim": 0.02, "win_reward_factor": 2 - }} + }, + "drop_ohlc_from_features": False + } return conf diff --git a/tests/freqai/test_freqai_backtesting.py b/tests/freqai/test_freqai_backtesting.py index 60963e762..0a8059966 100644 --- a/tests/freqai/test_freqai_backtesting.py +++ b/tests/freqai/test_freqai_backtesting.py @@ -35,8 +35,8 @@ def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, c args = get_args(args) bt_config = setup_optimize_configuration(args, RunMode.BACKTEST) Backtesting(bt_config) - assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical ' - 'populate_any_indicators.', caplog) + assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical', + caplog) Backtesting.cleanup() diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 5565bbed2..3b370aea4 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -1,5 +1,6 @@ import platform import shutil +import sys from pathlib import Path from unittest.mock import MagicMock @@ -13,10 +14,14 @@ from freqtrade.freqai.utils import download_all_data_for_training, get_required_ from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import Trade from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import create_mock_trades, get_patched_exchange, log_has_re +from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config +def is_py11() -> bool: + return sys.version_info >= (3, 11) + + def is_arm() -> bool: machine = platform.machine() return "arm" in machine or "aarch64" in machine @@ -27,6 +32,17 @@ def is_mac() -> bool: return "Darwin" in machine +def can_run_model(model: str) -> None: + if (is_arm() or is_py11()) and "Catboost" in model: + pytest.skip("CatBoost is not supported on ARM") + + if is_mac() and not is_arm() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning module not available on intel based Mac OS") + + if is_py11() and 'Reinforcement' in model: + pytest.skip("Reinforcement learning 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), ('XGBoostRegressor', False, True, False, True, False, 10), @@ -41,12 +57,7 @@ def is_mac() -> bool: def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32, can_short, shuffle, buffer): - if is_arm() and model == 'CatboostRegressor': - pytest.skip("CatBoost is not supported on ARM") - - if is_mac() and not is_arm() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning module not available on intel based Mac OS") - + can_run_model(model) model_save_ext = 'joblib' freqai_conf.update({"freqaimodel": model}) freqai_conf.update({"timerange": "20180110-20180130"}) @@ -57,13 +68,6 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle}) freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer}) - if 'ReinforcementLearner' in model: - model_save_ext = 'zip' - freqai_conf = make_rl_config(freqai_conf) - # test the RL guardrails - freqai_conf['freqai']['feature_parameters'].update({"use_SVM_to_remove_outliers": True}) - freqai_conf['freqai']['data_split_parameters'].update({'shuffle': True}) - if 'ReinforcementLearner' in model: model_save_ext = 'zip' freqai_conf = make_rl_config(freqai_conf) @@ -73,6 +77,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, if 'test_3ac' in model or 'test_4ac' in model: freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models") + freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) @@ -117,7 +122,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, ('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat") ]) def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat): - if is_arm() and 'Catboost' in model: + if (is_arm() or is_py11()) and 'Catboost' in model: pytest.skip("CatBoost is not supported on ARM") freqai_conf.update({"timerange": "20180110-20180130"}) @@ -159,7 +164,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s 'XGBoostRFClassifier', ]) def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): - if is_arm() and model == 'CatboostClassifier': + if (is_arm() or is_py11()) and model == 'CatboostClassifier': pytest.skip("CatBoost is not supported on ARM") freqai_conf.update({"freqaimodel": model}) @@ -206,13 +211,11 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): ], ) def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog): + can_run_model(model) + freqai_conf.get("freqai", {}).update({"save_backtest_models": True}) freqai_conf['runmode'] = RunMode.BACKTEST - if is_arm() and "Catboost" in model: - pytest.skip("CatBoost is not supported on ARM") - if is_mac() and 'Reinforcement' in model: - pytest.skip("Reinforcement learning module not available on intel based Mac OS") Trade.use_db = False freqai_conf.update({"freqaimodel": model}) @@ -509,6 +512,8 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers): if is_mac(): pytest.skip("Reinforcement learning module not available on intel based Mac OS") + if is_py11(): + pytest.skip("Reinforcement learning currently not available on python 3.11.") freqai_conf.update({"freqaimodel": "ReinforcementLearner"}) freqai_conf.update({"timerange": "20180110-20180130"}) @@ -520,7 +525,7 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers): strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) ticker_mock = MagicMock(return_value=tickers()['ETH/BTC']) - mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_mock) strategy.dp = DataProvider(freqai_conf, exchange) if not dp_exists: diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 4e78fc139..158dd04dc 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -5,10 +5,10 @@ from unittest.mock import MagicMock import pytest from freqtrade.data.history import get_timerange -from freqtrade.enums import ExitType +from freqtrade.enums import ExitType, TradingMode from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence.trade_model import LocalTrade -from tests.conftest import patch_exchange +from tests.conftest import EXMS, patch_exchange from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, _get_frame_time_from_offset, tests_timeframe) @@ -921,16 +921,18 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer) default_conf["use_exit_signal"] = data.use_exit_signal default_conf["max_open_trades"] = 10 - mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100) + mocker.patch(f"{EXMS}.get_fee", return_value=0.0) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=100) + mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) # TODO: Should we initialize this properly?? - backtesting._can_short = True + backtesting.trading_mode = TradingMode.MARGIN backtesting._set_strategy(backtesting.strategylist[0]) + backtesting._can_short = True backtesting.required_startup = 0 backtesting.strategy.advise_entry = lambda a, m: frame backtesting.strategy.advise_exit = lambda a, m: frame diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e407b4173..9dbda51b0 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -26,8 +26,8 @@ from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade, Trade from freqtrade.resolvers import StrategyResolver -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, log_has_re, + patch_exchange, patched_configuration_load_config_file) ORDER_TYPES = [ @@ -245,7 +245,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) def test_start(mocker, fee, default_conf, caplog) -> None: start_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) patched_configuration_load_config_file(mocker, default_conf) @@ -269,7 +269,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: """ default_conf["order_types"] = order_types patch_exchange(mocker) - get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + get_fee = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.config == default_conf @@ -290,7 +290,7 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'HyperoptableStrategy'] - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) with pytest.raises(OperationalException, match=r"Timeframe needs to be set in either configuration"): Backtesting(default_conf) @@ -300,7 +300,7 @@ def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.fee == 0.1234 @@ -344,7 +344,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None: assert backtesting.progress.progress == 0 -def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: +def test_backtesting_start(default_conf, mocker, caplog) -> None: def get_timerange(input1): return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59) @@ -367,6 +367,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.bot_loop_start = MagicMock() + backtesting.strategy.bot_start = MagicMock() backtesting.start() # check the logs, that will contain the backtest result exists = [ @@ -376,7 +377,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: for line in exists: assert log_has(line, caplog) assert backtesting.strategy.dp._pairlists is not None - assert backtesting.strategy.bot_loop_start.call_count == 1 + assert backtesting.strategy.bot_start.call_count == 1 + assert backtesting.strategy.bot_loop_start.call_count == 0 assert sbs.call_count == 1 assert sbc.call_count == 1 @@ -404,7 +406,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.data.history.history_utils.load_pair_history', MagicMock(return_value=pd.DataFrame())) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) @@ -436,9 +438,9 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') @@ -474,9 +476,9 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti def test_backtest__enter_trade(default_conf, fee, mocker) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001) + mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf')) patch_exchange(mocker) default_conf['stake_amount'] = 'unlimited' default_conf['max_open_trades'] = 2 @@ -525,7 +527,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: assert trade.stake_amount == 495 assert trade.is_short is True - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0) trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade assert trade.stake_amount == 300.0 @@ -533,10 +535,10 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: default_conf_usdt['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=100) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=100) mocker.patch("freqtrade.optimize.backtesting.price_to_precision", lambda p, *args: p) patch_exchange(mocker) default_conf_usdt['stake_amount'] = 300 @@ -564,7 +566,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: ] backtesting.strategy.leverage = MagicMock(return_value=5.0) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) # leverage = 5 @@ -601,7 +603,7 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: assert pytest.approx(trade.liquidation_price) == 0.11787191 # Stake-amount too high! - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0) trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is None @@ -616,9 +618,9 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['timeframe_detail'] = '1m' default_conf['max_open_trades'] = 2 @@ -681,9 +683,9 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -766,9 +768,9 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: @pytest.mark.parametrize('use_detail', [True, False]) def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None: default_conf_usdt['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) if use_detail: default_conf_usdt['timeframe_detail'] = '1m' patch_exchange(mocker) @@ -854,12 +856,12 @@ def test_backtest_one_detail_futures( default_conf_usdt['margin_mode'] = 'isolated' default_conf_usdt['candle_type_def'] = CandleType.FUTURES - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) default_conf_usdt['timeframe'] = '1h' if use_detail: @@ -945,12 +947,12 @@ def test_backtest_one_detail_futures_funding_fees( default_conf_usdt['minimal_roi'] = {'0': 1} default_conf_usdt['dry_run_wallet'] = 100000 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['XRP/USDT:USDT'])) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01)) default_conf_usdt['timeframe'] = '1h' if use_detail: @@ -1010,9 +1012,9 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) default_conf['startup_candle_count'] = 0 # Cancel unfilled order after 4 minutes on 5m timeframe. default_conf["unfilledtimeout"] = {"entry": 4} - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['max_open_trades'] = 1 backtesting = Backtesting(default_conf) @@ -1035,9 +1037,9 @@ def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 1 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1061,9 +1063,9 @@ def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> N default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1105,9 +1107,9 @@ def test_processed(default_conf, mocker, testdatadir) -> None: def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1155,9 +1157,9 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad default_conf['enable_protections'] = True default_conf['timeframe'] = '1m' default_conf['max_open_trades'] = 1 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) tests = [ ['sine', 9], ['raise', 10], @@ -1203,9 +1205,9 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, default_conf['protections'] = protections default_conf['enable_protections'] = True - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) # While entry-signals are unrealistic, running backtesting # over and over again should not cause different results @@ -1262,9 +1264,9 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) default_conf['max_open_trades'] = 10 backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) @@ -1310,9 +1312,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) dataframe['exit_short'] = 0 return dataframe - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 23b5eb93b..0d57ff89a 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -10,19 +10,19 @@ from arrow import Arrow from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.history import get_timerange -from freqtrade.enums import ExitType +from freqtrade.enums import ExitType, TradingMode from freqtrade.optimize.backtesting import Backtesting -from tests.conftest import patch_exchange +from tests.conftest import EXMS, patch_exchange def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_exit_signal'] = False default_conf['max_open_trades'] = 10 - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.amount_to_contract_precision', lambda x, *args, **kwargs: round(x, 8)) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf.update({ "stake_amount": 100.0, @@ -99,18 +99,19 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> ]) def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, leverage) -> None: default_conf['use_exit_signal'] = False - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=10) - mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10) + mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) patch_exchange(mocker) default_conf.update({ "stake_amount": 100.0, "dry_run_wallet": 1000.0, - "strategy": "StrategyTestV3" + "strategy": "StrategyTestV3", }) backtesting = Backtesting(default_conf) + backtesting.trading_mode = TradingMode.FUTURES backtesting._can_short = True backtesting._set_strategy(backtesting.strategylist[0]) pair = 'XRP/USDT' diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 8241a5362..64172bf1c 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, patch_exchange, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, patch_exchange, patched_configuration_load_config_file) @@ -71,7 +71,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N def test_start(mocker, fee, edge_conf, caplog) -> None: start_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) patch_exchange(mocker) mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock) patched_configuration_load_config_file(mocker, edge_conf) @@ -101,7 +101,7 @@ def test_edge_init_fee(mocker, edge_conf) -> None: patch_exchange(mocker) edge_conf['fee'] = 0.1234 edge_conf['stake_amount'] = 20 - fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) + fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.5) edge_cli = EdgeCli(edge_conf) assert edge_cli.edge.fee == 0.1234 assert fee_mock.call_count == 0 diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 36ceaeab2..786720030 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -20,7 +20,7 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.space import SKDecimal from freqtrade.strategy import IntParameter -from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_markets, log_has, log_has_re, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, get_markets, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) @@ -859,7 +859,7 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None: def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) # No hyperopt needed hyperopt_conf.update({ @@ -872,7 +872,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) - assert hyperopt.backtesting.strategy.bot_loop_started is True + assert hyperopt.backtesting.strategy.bot_started is True + assert hyperopt.backtesting.strategy.bot_loop_started is False assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert hyperopt.backtesting.strategy.buy_rsi.value == 35 @@ -897,10 +898,10 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, fee) -> None: - mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.Exchange._load_markets') - mocker.patch('freqtrade.exchange.Exchange.markets', + mocker.patch(f'{EXMS}.validate_config', MagicMock()) + mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f'{EXMS}._load_markets') + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=get_markets())) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) # No hyperopt needed @@ -922,7 +923,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) - assert hyperopt.backtesting.strategy.bot_loop_started is True + assert hyperopt.backtesting.strategy.bot_started is True + assert hyperopt.backtesting.strategy.bot_loop_started is False assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert hyperopt.backtesting.strategy.buy_rsi.value == 35 @@ -938,7 +940,7 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee) -> None: patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ @@ -959,7 +961,8 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee) hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) - assert hyperopt.backtesting.strategy.bot_loop_started is True + assert hyperopt.backtesting.strategy.bot_loop_started is False + assert hyperopt.backtesting.strategy.bot_started is True assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert hyperopt.backtesting.strategy.buy_rsi.value == 35 @@ -996,7 +999,7 @@ def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmpdir, f # This test is to ensure that unlimited max_open_trades are ignored for the backtesting # if we have an unlimited stake amount patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ 'strategy': 'HyperoptableStrategy', @@ -1024,7 +1027,7 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmpdir, fee, capsys) -> Non # This test is to ensure that after hyperopting, max_open_trades is never # saved as inf in the output json params patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ 'strategy': 'HyperoptableStrategy', @@ -1070,7 +1073,7 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmpdir, fee) -> None # This test is to ensure that max_open_trades is the same across all functions needing it # after it has been changed from the hyperopt patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0) + mocker.patch(f'{EXMS}.get_fee', return_value=0) (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) hyperopt_conf.update({ diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 549202284..0cc32baaf 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -236,7 +236,7 @@ def test_store_backtest_candles(testdatadir, mocker): assert dump_mock.call_count == 1 assert isinstance(dump_mock.call_args_list[0][0][0], Path) - assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) + assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl') dump_mock.reset_mock() # mock file exporting @@ -245,7 +245,7 @@ def test_store_backtest_candles(testdatadir, mocker): assert dump_mock.call_count == 1 assert isinstance(dump_mock.call_args_list[0][0][0], Path) # result will be testdatadir / testresult-_signals.pkl - assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) + assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl') dump_mock.reset_mock() @@ -255,7 +255,7 @@ def test_write_read_backtest_candles(tmpdir): # test directory exporting stored_file = store_backtest_signal_candles(Path(tmpdir), candle_dict, '2022_01_01_15_05_13') - scp = open(stored_file, "rb") + scp = stored_file.open("rb") pickled_signal_candles = joblib.load(scp) scp.close() @@ -269,7 +269,7 @@ def test_write_read_backtest_candles(tmpdir): # test file exporting filename = Path(tmpdir / 'testresult') stored_file = store_backtest_signal_candles(filename, candle_dict, '2022_01_01_15_05_13') - scp = open(stored_file, "rb") + scp = stored_file.open("rb") pickled_signal_candles = joblib.load(scp) scp.close() diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index 2a6959d58..854d39994 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -4,7 +4,7 @@ from pathlib import Path from unittest.mock import MagicMock import pytest -from sqlalchemy import create_engine, text +from sqlalchemy import create_engine, select, text from freqtrade.constants import DEFAULT_DB_PROD_URL from freqtrade.enums import TradingMode @@ -21,8 +21,8 @@ spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURE def test_init_create_session(default_conf): # Check if init create a session init_db(default_conf['db_url']) - assert hasattr(Trade, '_session') - assert 'scoped_session' in type(Trade._session).__name__ + assert hasattr(Trade, 'session') + assert 'scoped_session' in type(Trade.session).__name__ def test_init_custom_db_url(default_conf, tmpdir): @@ -34,7 +34,7 @@ def test_init_custom_db_url(default_conf, tmpdir): init_db(default_conf['db_url']) assert Path(filename).is_file() - r = Trade._session.execute(text("PRAGMA journal_mode")) + r = Trade.session.execute(text("PRAGMA journal_mode")) assert r.first() == ('wal',) @@ -235,8 +235,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog): # Run init to test migration init_db(default_conf['db_url']) - assert len(Trade.query.filter(Trade.id == 1).all()) == 1 - trade = Trade.query.filter(Trade.id == 1).first() + trades = Trade.session.scalars(select(Trade).filter(Trade.id == 1)).all() + assert len(trades) == 1 + trade = trades[0] assert trade.fee_open == fee.return_value assert trade.fee_close == fee.return_value assert trade.open_rate_requested is None @@ -404,9 +405,9 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog): init_db(default_conf['db_url']) - assert len(PairLock.query.all()) == 2 - assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1 - pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all() + assert len(PairLock.get_all_locks().all()) == 2 + assert len(PairLock.session.scalars(select(PairLock).filter(PairLock.pair == '*')).all()) == 1 + pairlocks = PairLock.session.scalars(select(PairLock).filter(PairLock.pair == 'ETH/BTC')).all() assert len(pairlocks) == 1 pairlocks[0].pair == 'ETH/BTC' pairlocks[0].side == '*' diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index d06f05179..23ec6d4fb 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -4,6 +4,7 @@ from types import FunctionType import arrow import pytest +from sqlalchemy import select from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.enums import TradingMode @@ -1329,70 +1330,78 @@ def test_to_json(fee): open_rate=0.123, exchange='binance', enter_tag=None, - open_order_id='dry_run_buy_12345' + open_order_id='dry_run_buy_12345', + precision_mode=1, + amount_precision=8.0, + price_precision=7.0, ) result = trade.to_json() assert isinstance(result, dict) - assert result == {'trade_id': None, - 'pair': 'ADA/USDT', - 'base_currency': 'ADA', - 'quote_currency': 'USDT', - 'is_open': None, - 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), - 'open_timestamp': int(trade.open_date.timestamp() * 1000), - 'open_order_id': 'dry_run_buy_12345', - 'close_date': None, - 'close_timestamp': None, - 'open_rate': 0.123, - 'open_rate_requested': None, - 'open_trade_value': 15.1668225, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'close_rate': None, - 'close_rate_requested': None, - 'amount': 123.0, - 'amount_requested': 123.0, - 'stake_amount': 0.001, - 'max_stake_amount': None, - 'trade_duration': None, - 'trade_duration_s': None, - 'realized_profit': 0.0, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'profit_ratio': None, - 'profit_pct': None, - 'profit_abs': None, - 'exit_reason': None, - 'exit_order_status': None, - 'stop_loss_abs': None, - 'stop_loss_ratio': None, - 'stop_loss_pct': None, - 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss_abs': None, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'min_rate': None, - 'max_rate': None, - 'strategy': None, - 'enter_tag': None, - 'timeframe': None, - 'exchange': 'binance', - 'leverage': None, - 'interest_rate': None, - 'liquidation_price': None, - 'is_short': None, - 'trading_mode': None, - 'funding_fees': None, - 'orders': [], - } + assert result == { + 'trade_id': None, + 'pair': 'ADA/USDT', + 'base_currency': 'ADA', + 'quote_currency': 'USDT', + 'is_open': None, + 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), + 'open_timestamp': int(trade.open_date.timestamp() * 1000), + 'open_order_id': 'dry_run_buy_12345', + 'close_date': None, + 'close_timestamp': None, + 'open_rate': 0.123, + 'open_rate_requested': None, + 'open_trade_value': 15.1668225, + 'fee_close': 0.0025, + 'fee_close_cost': None, + 'fee_close_currency': None, + 'fee_open': 0.0025, + 'fee_open_cost': None, + 'fee_open_currency': None, + 'close_rate': None, + 'close_rate_requested': None, + 'amount': 123.0, + 'amount_requested': 123.0, + 'stake_amount': 0.001, + 'max_stake_amount': None, + 'trade_duration': None, + 'trade_duration_s': None, + 'realized_profit': 0.0, + 'realized_profit_ratio': None, + 'close_profit': None, + 'close_profit_pct': None, + 'close_profit_abs': None, + 'profit_ratio': None, + 'profit_pct': None, + 'profit_abs': None, + 'exit_reason': None, + 'exit_order_status': None, + 'stop_loss_abs': None, + 'stop_loss_ratio': None, + 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, + 'initial_stop_loss_abs': None, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, + 'min_rate': None, + 'max_rate': None, + 'strategy': None, + 'enter_tag': None, + 'timeframe': None, + 'exchange': 'binance', + 'leverage': None, + 'interest_rate': None, + 'liquidation_price': None, + 'is_short': None, + 'trading_mode': None, + 'funding_fees': None, + 'amount_precision': 8.0, + 'price_precision': 7.0, + 'precision_mode': 1, + 'orders': [], + } # Simulate dry_run entries trade = Trade( @@ -1408,69 +1417,77 @@ def test_to_json(fee): close_rate=0.125, enter_tag='buys_signal_001', exchange='binance', + precision_mode=2, + amount_precision=7.0, + price_precision=8.0, ) result = trade.to_json() assert isinstance(result, dict) - assert result == {'trade_id': None, - 'pair': 'XRP/BTC', - 'base_currency': 'XRP', - 'quote_currency': 'BTC', - 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), - 'open_timestamp': int(trade.open_date.timestamp() * 1000), - 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), - 'close_timestamp': int(trade.close_date.timestamp() * 1000), - 'open_rate': 0.123, - 'close_rate': 0.125, - 'amount': 100.0, - 'amount_requested': 101.0, - 'stake_amount': 0.001, - 'max_stake_amount': None, - 'trade_duration': 60, - 'trade_duration_s': 3600, - 'stop_loss_abs': None, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, - 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss_abs': None, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'realized_profit': 0.0, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'profit_ratio': None, - 'profit_pct': None, - 'profit_abs': None, - 'close_rate_requested': None, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'is_open': None, - 'max_rate': None, - 'min_rate': None, - 'open_order_id': None, - 'open_rate_requested': None, - 'open_trade_value': 12.33075, - 'exit_reason': None, - 'exit_order_status': None, - 'strategy': None, - 'enter_tag': 'buys_signal_001', - 'timeframe': None, - 'exchange': 'binance', - 'leverage': None, - 'interest_rate': None, - 'liquidation_price': None, - 'is_short': None, - 'trading_mode': None, - 'funding_fees': None, - 'orders': [], - } + assert result == { + 'trade_id': None, + 'pair': 'XRP/BTC', + 'base_currency': 'XRP', + 'quote_currency': 'BTC', + 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), + 'open_timestamp': int(trade.open_date.timestamp() * 1000), + 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), + 'close_timestamp': int(trade.close_date.timestamp() * 1000), + 'open_rate': 0.123, + 'close_rate': 0.125, + 'amount': 100.0, + 'amount_requested': 101.0, + 'stake_amount': 0.001, + 'max_stake_amount': None, + 'trade_duration': 60, + 'trade_duration_s': 3600, + 'stop_loss_abs': None, + 'stop_loss_pct': None, + 'stop_loss_ratio': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, + 'initial_stop_loss_abs': None, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, + 'realized_profit': 0.0, + 'realized_profit_ratio': None, + 'close_profit': None, + 'close_profit_pct': None, + 'close_profit_abs': None, + 'profit_ratio': None, + 'profit_pct': None, + 'profit_abs': None, + 'close_rate_requested': None, + 'fee_close': 0.0025, + 'fee_close_cost': None, + 'fee_close_currency': None, + 'fee_open': 0.0025, + 'fee_open_cost': None, + 'fee_open_currency': None, + 'is_open': None, + 'max_rate': None, + 'min_rate': None, + 'open_order_id': None, + 'open_rate_requested': None, + 'open_trade_value': 12.33075, + 'exit_reason': None, + 'exit_order_status': None, + 'strategy': None, + 'enter_tag': 'buys_signal_001', + 'timeframe': None, + 'exchange': 'binance', + 'leverage': None, + 'interest_rate': None, + 'liquidation_price': None, + 'is_short': None, + 'trading_mode': None, + 'funding_fees': None, + 'amount_precision': 7.0, + 'price_precision': 8.0, + 'precision_mode': 2, + 'orders': [], + } def test_stoploss_reinitialization(default_conf, fee): @@ -1492,7 +1509,7 @@ def test_stoploss_reinitialization(default_conf, fee): assert trade.stop_loss_pct == -0.05 assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss_pct == -0.05 - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() # Lower stoploss @@ -1554,7 +1571,7 @@ def test_stoploss_reinitialization_leverage(default_conf, fee): assert trade.stop_loss_pct == -0.1 assert trade.initial_stop_loss == 0.98 assert trade.initial_stop_loss_pct == -0.1 - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() # Lower stoploss @@ -1616,7 +1633,7 @@ def test_stoploss_reinitialization_short(default_conf, fee): assert trade.stop_loss_pct == -0.1 assert trade.initial_stop_loss == 1.02 assert trade.initial_stop_loss_pct == -0.1 - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() # Lower stoploss Trade.stoploss_reinitialization(-0.15) @@ -1791,17 +1808,17 @@ def test_get_trades_proxy(fee, use_db, is_short): @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize('is_short', [True, False]) def test_get_trades__query(fee, is_short): - query = Trade.get_trades([]) + query = Trade.get_trades_query([]) # without orders there should be no join issued. - query1 = Trade.get_trades([], include_orders=False) + query1 = Trade.get_trades_query([], include_orders=False) # Empty "with-options -> default - selectin" assert query._with_options == () assert query1._with_options != () create_mock_trades(fee, is_short) - query = Trade.get_trades([]) - query1 = Trade.get_trades([], include_orders=False) + query = Trade.get_trades_query([]) + query1 = Trade.get_trades_query([], include_orders=False) assert query._with_options == () assert query1._with_options != () @@ -2014,6 +2031,7 @@ def test_Trade_object_idem(): 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', + 'get_trades_query', 'get_exit_reason_performance', 'get_enter_tag_performance', 'get_mix_tag_performance', @@ -2440,8 +2458,9 @@ def test_select_filled_orders(fee): def test_order_to_ccxt(limit_buy_order_open): order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy') - order.query.session.add(order) - Order.query.session.commit() + order.ft_trade_id = 1 + order.session.add(order) + Order.session.commit() order_resp = Order.order_by_id(limit_buy_order_open['id']) assert order_resp @@ -2543,7 +2562,7 @@ def test_recalc_trade_from_orders_dca(data) -> None: leverage=1.0, trading_mode=TradingMode.SPOT ) - Trade.query.session.add(trade) + Trade.session.add(trade) for idx, (order, result) in enumerate(data['orders']): amount = order[1] @@ -2572,11 +2591,11 @@ def test_recalc_trade_from_orders_dca(data) -> None: trade.recalc_trade_from_orders() Trade.commit() - orders1 = Order.query.all() + orders1 = Order.session.scalars(select(Order)).all() assert orders1 assert len(orders1) == idx + 1 - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert len(trade.orders) == idx + 1 if idx < len(data) - 1: @@ -2593,6 +2612,6 @@ def test_recalc_trade_from_orders_dca(data) -> None: assert pytest.approx(trade.close_profit_abs) == data['end_profit'] assert pytest.approx(trade.close_profit) == data['end_profit_ratio'] assert not trade.is_open - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None diff --git a/tests/persistence/test_trade_fromjson.py b/tests/persistence/test_trade_fromjson.py index 529008e02..22053463d 100644 --- a/tests/persistence/test_trade_fromjson.py +++ b/tests/persistence/test_trade_fromjson.py @@ -50,8 +50,8 @@ def test_trade_fromjson(): "stop_loss_ratio": -0.216, "stop_loss_pct": -21.6, "stoploss_order_id": null, - "stoploss_last_update": null, - "stoploss_last_update_timestamp": null, + "stoploss_last_update": "2022-10-18 09:13:42", + "stoploss_last_update_timestamp": 1666077222000, "initial_stop_loss_abs": 0.1981, "initial_stop_loss_ratio": -0.216, "initial_stop_loss_pct": -21.6, diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 739c3a7ac..bc8fe84f1 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -18,8 +18,8 @@ from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.resolvers import PairListResolver -from tests.conftest import (create_mock_trades_usdt, get_patched_exchange, get_patched_freqtradebot, - log_has, log_has_re, num_log_has) +from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange, + get_patched_freqtradebot, log_has, log_has_re, num_log_has) # Exclude RemotePairList from tests. @@ -116,7 +116,7 @@ def static_pl_conf(whitelist_conf): def test_log_cached(mocker, static_pl_conf, markets, tickers): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -139,7 +139,7 @@ def test_log_cached(mocker, static_pl_conf, markets, tickers): def test_load_pairlist_noexist(mocker, markets, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) plm = PairListManager(freqtrade.exchange, default_conf, MagicMock()) with pytest.raises(OperationalException, match=r"Impossible to load Pairlist 'NonexistingPairList'. " @@ -150,7 +150,7 @@ def test_load_pairlist_noexist(mocker, markets, default_conf): def test_load_pairlist_verify_multi(mocker, markets_static, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_static)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets_static)) plm = PairListManager(freqtrade.exchange, default_conf, MagicMock()) # Call different versions one after the other, should always consider what was passed in # and have no side-effects (therefore the same check multiple times) @@ -166,7 +166,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) freqtrade.pairlists.refresh_pairlist() # List ordered by BaseVolume whitelist = ['ETH/BTC', 'TKN/BTC'] @@ -180,7 +180,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): def test_refresh_static_pairlist(mocker, markets, static_pl_conf): freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -204,7 +204,7 @@ def test_refresh_static_pairlist_noexist(mocker, markets, static_pl_conf, pairs, static_pl_conf['exchange']['pair_whitelist'] += pairs freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -221,7 +221,7 @@ def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog): static_pl_conf['exchange']['pair_blacklist'] = ['*/BTC'] freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -237,7 +237,7 @@ def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_c logger = logging.getLogger(__name__) freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), markets=PropertyMock(return_value=markets), ) @@ -264,14 +264,14 @@ def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_c def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=True), ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=shitcoinmarkets), ) # argument: use the whitelist dynamically by exchange-volume @@ -291,7 +291,7 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ tickers_dict = tickers() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) # Remove caching of ticker data to emulate changing volume by the time of second call @@ -302,7 +302,7 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_2) # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=shitcoinmarkets), ) @@ -320,11 +320,11 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, exchange_has=MagicMock(return_value=True), ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets_empty)) # argument: use the whitelist dynamically by exchange-volume whitelist = [] @@ -523,15 +523,15 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola, } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -649,7 +649,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_volume, } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) if volumefilter_result == 'default_refresh_too_short': with pytest.raises(OperationalException, @@ -675,7 +675,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, else: freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) @@ -687,7 +687,7 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_data = [] mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -702,7 +702,7 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None: whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}] del whitelist_conf['stoploss'] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r"PrecisionFilter can only work with stoploss defined\..*"): @@ -711,9 +711,9 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None: def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None: whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] - if hasattr(Trade, 'query'): - del Trade.query - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + if hasattr(Trade, 'session'): + del Trade.session + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) exchange = get_patched_exchange(mocker, whitelist_conf) pm = PairListManager(exchange, whitelist_conf, MagicMock()) pm.refresh_pairlist() @@ -755,7 +755,7 @@ def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> N {"method": "StaticPairList"}, {"method": "PerformanceFilter", "minutes": 60, "min_profit": 0.01} ] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) exchange = get_patched_exchange(mocker, default_conf_usdt) pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() @@ -781,7 +781,7 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog {"method": "StaticPairList", "allow_inactive": True}, {"method": "PerformanceFilter", "minutes": 60, } ] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) exchange = get_patched_exchange(mocker, default_conf_usdt) pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() @@ -806,7 +806,7 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=False), ) @@ -819,7 +819,7 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> None: default_conf['pairlists'] = [{'method': 'StaticPairList'}, {'method': 'SpreadFilter'}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, exchange_has=MagicMock(return_value=False), ) @@ -828,11 +828,17 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N match=r'Exchange does not support fetchTickers, .*'): get_patched_freqtradebot(mocker, default_conf) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.get_option', MagicMock(return_value=False)) + with pytest.raises(OperationalException, + match=r'.*requires exchange to have bid/ask data'): + get_patched_freqtradebot(mocker, default_conf) + @pytest.mark.parametrize("pairlist", TESTABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True) ) @@ -861,7 +867,7 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, log_message, tickers): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -881,10 +887,10 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, tickers): whitelist_conf['pairlists'][0]['method'] = pairlist - mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=None), get_tickers=tickers ) @@ -897,7 +903,7 @@ def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, t def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf): whitelist_conf['pairlists'][0].update({"sort_key": "asdf"}) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r"key asdf not in .*"): get_patched_freqtradebot(mocker, whitelist_conf) @@ -905,7 +911,7 @@ def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf): def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -925,7 +931,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': -1}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -941,7 +947,7 @@ def test_agefilter_max_days_lower_than_min_days(mocker, default_conf, markets, t {'method': 'AgeFilter', 'min_days_listed': 3, "max_days_listed": 2}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -956,7 +962,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': 99999}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -976,7 +982,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers, @@ -1000,14 +1006,14 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history.iloc[[0]], } - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 3 assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1 # Move to next day t.move_to("2021-09-02 01:00:00 +00:00") - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 3 assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1 @@ -1021,7 +1027,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history, } - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() assert len(freqtrade.pairlists.whitelist) == 4 # Called once (only for XRP/BTC) @@ -1033,7 +1039,7 @@ def test_OffsetFilter_error(mocker, whitelist_conf) -> None: [{"method": "StaticPairList"}, {"method": "OffsetFilter", "offset": -1}] ) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, match=r'OffsetFilter requires offset to be >= 0'): @@ -1044,7 +1050,7 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'RangeStabilityFilter', 'lookback_days': 99999}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1074,7 +1080,7 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh 'min_rate_of_change': min_rate_of_change, "max_rate_of_change": max_rate_of_change}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1088,7 +1094,7 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh ('BLK/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) @@ -1109,7 +1115,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'SpreadFilter', 'max_spread_ratio': 0.1}] - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers @@ -1123,7 +1129,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo tickers.return_value['ETH/BTC']['ask'] = 0.0 del tickers.return_value['TKN/BTC'] del tickers.return_value['LTC/BTC'] - mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers) + mocker.patch.multiple(EXMS, get_tickers=tickers) ftbot.pairlists.refresh_pairlist() assert log_has_re(r'Removed .* invalid ticker data.*', caplog) @@ -1197,7 +1203,7 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, desc_expected, exception_expected): - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True) ) @@ -1214,7 +1220,7 @@ def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) whitelist_conf['pairlists'] = [] @@ -1266,14 +1272,14 @@ def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, o allowlist_conf['pairlists'] = pairlists allowlist_conf['exchange']['pair_whitelist'] = pair_allowlist - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, allowlist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_tickers=tickers, markets=PropertyMock(return_value=markets) ) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch.multiple(EXMS, get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), ) mocker.patch.multiple('freqtrade.persistence.Trade', @@ -1371,7 +1377,7 @@ def test_expand_pairlist_keep_invalid(wildcardlist, pairs, expected): def test_ProducerPairlist_no_emc(mocker, whitelist_conf): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) whitelist_conf['pairlists'] = [ { @@ -1388,8 +1394,8 @@ def test_ProducerPairlist_no_emc(mocker, whitelist_conf): def test_ProducerPairlist(mocker, whitelist_conf, markets): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - mocker.patch.multiple('freqtrade.exchange.Exchange', + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), ) diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index 0ba9bb746..6b7112f98 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -14,7 +14,7 @@ def test_PairLocks(use_db): PairLocks.use_db = use_db # No lock should be present if use_db: - assert len(PairLock.query.all()) == 0 + assert len(PairLock.get_all_locks().all()) == 0 assert PairLocks.use_db == use_db @@ -88,13 +88,13 @@ def test_PairLocks(use_db): if use_db: locks = PairLocks.get_all_locks() - locks_db = PairLock.query.all() + locks_db = PairLock.get_all_locks().all() assert len(locks) == len(locks_db) assert len(locks_db) > 0 else: # Nothing was pushed to the database assert len(PairLocks.get_all_locks()) > 0 - assert len(PairLock.query.all()) == 0 + assert len(PairLock.get_all_locks().all()) == 0 # Reset use-db variable PairLocks.reset_locks() PairLocks.use_db = True @@ -107,7 +107,7 @@ def test_PairLocks_getlongestlock(use_db): # No lock should be present PairLocks.use_db = use_db if use_db: - assert len(PairLock.query.all()) == 0 + assert len(PairLock.get_all_locks().all()) == 0 assert PairLocks.use_db == use_db @@ -139,7 +139,7 @@ def test_PairLocks_reason(use_db): PairLocks.use_db = use_db # No lock should be present if use_db: - assert len(PairLock.query.all()) == 0 + assert len(PairLock.get_all_locks().all()) == 0 assert PairLocks.use_db == use_db diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 2bbdf3d4f..5e6128c73 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -74,7 +74,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, trade.close(close_price) trade.exit_reason = exit_reason - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() return trade diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 31e19ce3f..ff08a0564 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1,12 +1,10 @@ -# pragma pylint: disable=missing-docstring, C0103 -# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments - from copy import deepcopy from datetime import datetime, timedelta, timezone from unittest.mock import ANY, MagicMock, PropertyMock import pytest from numpy import isnan +from sqlalchemy import select from freqtrade.edge import PairInfo from freqtrade.enums import SignalDirection, State, TradingMode @@ -15,19 +13,10 @@ from freqtrade.persistence import Trade from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter -from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, - patch_get_signal) +from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, + get_patched_freqtradebot, patch_get_signal) -# Functions for recurrent object patching -def prec_satoshi(a, b) -> float: - """ - :return: True if A and B differs less than one satoshi. - """ - return abs(a - b) < 0.00000001 - - -# Unit tests def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: gen_response = { 'trade_id': 1, @@ -62,15 +51,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'amount': 91.07468123, 'amount_requested': 91.07468124, 'stake_amount': 0.001, - 'max_stake_amount': ANY, + 'max_stake_amount': None, 'trade_duration': None, 'trade_duration_s': None, 'close_profit': None, 'close_profit_pct': None, 'close_profit_abs': None, - 'current_profit': -0.00408133, - 'current_profit_pct': -0.41, - 'current_profit_abs': -4.09e-06, 'profit_ratio': -0.00408133, 'profit_pct': -0.41, 'profit_abs': -4.09e-06, @@ -91,6 +77,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10376381, 'open_order': None, 'realized_profit': 0.0, + 'realized_profit_ratio': None, + 'total_profit_abs': -4.09e-06, + 'total_profit_fiat': ANY, + 'total_profit_ratio': None, 'exchange': 'binance', 'leverage': 1.0, 'interest_rate': 0.0, @@ -98,6 +88,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'is_short': False, 'funding_fees': 0.0, 'trading_mode': TradingMode.SPOT, + 'amount_precision': 8.0, + 'price_precision': 8.0, + 'precision_mode': 2, 'orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', @@ -109,7 +102,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: } mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(side_effect=[False, True]), @@ -134,20 +127,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': 0.0, 'profit_pct': 0.0, 'profit_abs': 0.0, - 'current_profit': 0.0, - 'current_profit_pct': 0.0, - 'current_profit_abs': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, - 'stoploss_current_dist': -1.099e-05, - 'stoploss_current_dist_ratio': -1.0, - 'stoploss_current_dist_pct': pytest.approx(-100.0), - 'stoploss_entry_dist': -0.0010025, - 'stoploss_entry_dist_ratio': -1.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, + 'total_profit_abs': 0.0, 'open_order': '(limit buy rem=91.07468123)', }) response_unfilled['orders'][0].update({ @@ -182,12 +162,16 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: results = rpc._rpc_trade_status() response = deepcopy(gen_response) + response.update({ + 'max_stake_amount': 0.001, + 'total_profit_ratio': pytest.approx(-0.00409), + }) assert results[0] == response - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() - assert isnan(results[0]['current_profit']) + assert isnan(results[0]['profit_ratio']) assert isnan(results[0]['current_rate']) response_norate = deepcopy(gen_response) # Update elements that are NaN when no rate is available. @@ -195,12 +179,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_current_dist': ANY, 'stoploss_current_dist_ratio': ANY, 'stoploss_current_dist_pct': ANY, + 'max_stake_amount': 0.001, 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, - 'current_profit_abs': ANY, - 'current_profit': ANY, - 'current_profit_pct': ANY, + 'total_profit_abs': ANY, + 'total_profit_ratio': ANY, 'current_rate': ANY, }) assert results[0] == response_norate @@ -214,7 +198,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -226,7 +210,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) freqtradebot.enter_positions() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -237,7 +221,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert '0.00' == result[0][3] assert isnan(fiat_profit_sum) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtradebot.process() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -248,7 +232,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert '-0.41%' == result[0][3] assert isnan(fiat_profit_sum) - # Test with fiatconvert + # Test with fiat convert rpc._fiat_converter = CryptoToFiatConverter() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers @@ -268,7 +252,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: # 3 on top of the initial one. assert result[0][4] == '1/4' - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert 'instantly' == result[0][2] @@ -281,7 +265,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) @@ -322,7 +306,7 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee, def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -350,7 +334,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), cancel_order=cancel_mock, cancel_stoploss_order=stoploss_mock, @@ -363,7 +347,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): with pytest.raises(RPCException, match='invalid argument'): rpc._rpc_delete('200') - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() trades[1].stoploss_order_id = '1234' trades[2].stoploss_order_id = '1234' assert len(trades) > 2 @@ -384,15 +368,13 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): assert stoploss_mock.call_count == 1 assert res['cancel_order_count'] == 2 - stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - side_effect=InvalidOrderException) + stoploss_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException) res = rpc._rpc_delete('3') assert stoploss_mock.call_count == 1 stoploss_mock.reset_mock() - cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order', - side_effect=InvalidOrderException) + cancel_mock = mocker.patch(f'{EXMS}.cancel_order', side_effect=InvalidOrderException) res = rpc._rpc_delete('4') assert cancel_mock.call_count == 1 @@ -403,7 +385,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -440,7 +422,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert stats['best_rate'] == 10.0 # Test non-available pair - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available"))) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert stats['trade_count'] == 7 @@ -474,7 +456,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=mock_balance), get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) ) @@ -537,7 +519,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, validate_trading_mode_and_margin_mode=MagicMock(), get_balances=MagicMock(return_value=mock_balance), fetch_positions=MagicMock(return_value=mock_pos), @@ -553,8 +535,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 30.30909624) - assert prec_satoshi(result['value'], 454636.44360691) + assert pytest.approx(result['total']) == 30.30909624 + assert pytest.approx(result['value']) == 454636.44360691 assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] @@ -614,7 +596,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): def test_rpc_start(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -635,7 +617,7 @@ def test_rpc_start(mocker, default_conf) -> None: def test_rpc_stop(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -657,7 +639,7 @@ def test_rpc_stop(mocker, default_conf) -> None: def test_rpc_stopentry(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock() ) @@ -677,7 +659,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, cancel_order=cancel_order_mock, fetch_order=MagicMock( @@ -725,15 +707,14 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 - mocker.patch( - 'freqtrade.exchange.Exchange._dry_is_price_crossed', MagicMock(return_value=False)) + mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False)) freqtradebot.enter_positions() # make an limit-buy open trade - trade = Trade.query.filter(Trade.id == '3').first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() filled_amount = trade.amount / 2 # Fetch order - it's open first, and closed after cancel_order is called. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', side_effect=[{ 'id': trade.orders[0].order_id, 'status': 'open', @@ -755,7 +736,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: assert pytest.approx(trade.amount) == filled_amount mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -765,11 +746,11 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.config['max_open_trades'] = 3 freqtradebot.enter_positions() - trade = Trade.query.filter(Trade.id == '2').first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -783,11 +764,11 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: assert cancel_order_mock.call_count == 2 assert trade.amount == amount - trade = Trade.query.filter(Trade.id == '3').first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() # make an limit-sell open trade mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', return_value={ 'status': 'open', 'type': 'limit', @@ -807,7 +788,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -830,7 +811,7 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -862,7 +843,7 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -875,23 +856,23 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['enter_tag'] == 'TEST1' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['enter_tag'] == 'Other' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_enter_tag_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['enter_tag'] == 'TEST1' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -918,7 +899,7 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -931,23 +912,23 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['exit_reason'] == 'sell_signal' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['exit_reason'] == 'roi' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_exit_reason_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['exit_reason'] == 'sell_signal' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -971,7 +952,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) @@ -984,10 +965,10 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['mix_tag'] == 'TEST1 sell_signal' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 assert res[1]['mix_tag'] == 'Other roi' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit_pct'], 1.0) + assert pytest.approx(res[1]['profit_pct']) == 1.0 # Test for a specific pair res = rpc._rpc_mix_tag_performance('ETC/BTC') @@ -995,13 +976,13 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['mix_tag'] == 'TEST1 sell_signal' - assert prec_satoshi(res[0]['profit_pct'], 0.5) + assert pytest.approx(res[0]['profit_pct']) == 0.5 def test_rpc_count(mocker, default_conf, ticker, fee) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1026,7 +1007,7 @@ def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) buy_mm = MagicMock(return_value=limit_buy_order_open) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1155,7 +1136,7 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 4, }] - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -1252,6 +1233,6 @@ def test_rpc_health(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) - result = rpc._health() - assert result['last_process'] == '1970-01-01 00:00:00+00:00' - assert result['last_process_ts'] == 0 + result = rpc.health() + assert result['last_process'] is None + assert result['last_process_ts'] is None diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 43d9abb78..31075e514 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1,6 +1,7 @@ """ Unit test file for rpc/api_server.py """ +import asyncio import logging import time from datetime import datetime, timedelta, timezone @@ -14,6 +15,7 @@ from fastapi import FastAPI, WebSocketDisconnect from fastapi.exceptions import HTTPException from fastapi.testclient import TestClient from requests.auth import _basic_auth_str +from sqlalchemy import select from freqtrade.__init__ import __version__ from freqtrade.enums import CandleType, RunMode, State, TradingMode @@ -24,7 +26,7 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer -from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro, +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has, log_has_re, patch_get_signal) @@ -298,10 +300,6 @@ def test_api_UvicornServer(mocker): s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) assert thread_mock.call_count == 0 - s.install_signal_handlers() - # Original implementation starts a thread - make sure that's not the case - assert thread_mock.call_count == 0 - # Fake started to avoid sleeping forever s.started = True s.run_in_thread() @@ -317,10 +315,6 @@ def test_api_UvicornServer_run(mocker): s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) assert serve_mock.call_count == 0 - s.install_signal_handlers() - # Original implementation starts a thread - make sure that's not the case - assert serve_mock.call_count == 0 - # Fake started to avoid sleeping forever s.started = True s.run() @@ -330,13 +324,10 @@ def test_api_UvicornServer_run(mocker): def test_api_UvicornServer_run_no_uvloop(mocker, import_fails): serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve', get_mock_coro(None)) + asyncio.set_event_loop(asyncio.new_event_loop()) s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) assert serve_mock.call_count == 0 - s.install_signal_handlers() - # Original implementation starts a thread - make sure that's not the case - assert serve_mock.call_count == 0 - # Fake started to avoid sleeping forever s.started = True s.run() @@ -473,9 +464,9 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): ftbot, client = botclient ftbot.config['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', + mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") ftbot.wallets.update() @@ -507,7 +498,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -594,7 +585,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -613,7 +604,7 @@ def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets) ) rc = client_get(client, f"{BASE_URI}/trades") @@ -624,7 +615,7 @@ def test_api_trades(botclient, mocker, fee, markets, is_short): assert rc.json()['offset'] == 0 create_mock_trades(fee, is_short=is_short) - Trade.query.session.flush() + Trade.session.flush() rc = client_get(client, f"{BASE_URI}/trades") assert_response(rc) @@ -644,7 +635,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), fetch_ticker=ticker, ) @@ -652,7 +643,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): assert_response(rc, 404) assert rc.json()['detail'] == 'Trade not found.' - Trade.query.session.rollback() + Trade.rollback() create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/trade/3") @@ -668,7 +659,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), cancel_order=cancel_mock, cancel_stoploss_order=stoploss_mock, @@ -677,7 +668,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): create_mock_trades(fee, is_short=is_short) ftbot.strategy.order_types['stoploss_on_exchange'] = True - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() trades[1].stoploss_order_id = '1234' Trade.commit() assert len(trades) > 2 @@ -685,7 +676,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): rc = client_delete(client, f"{BASE_URI}/trades/1") assert_response(rc) assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.' - assert len(trades) - 1 == len(Trade.query.all()) + assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all()) assert cancel_mock.call_count == 1 cancel_mock.reset_mock() @@ -694,11 +685,11 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert_response(rc, 502) assert cancel_mock.call_count == 0 - assert len(trades) - 1 == len(Trade.query.all()) + assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all()) rc = client_delete(client, f"{BASE_URI}/trades/2") assert_response(rc) assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.' - assert len(trades) - 2 == len(Trade.query.all()) + assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all()) assert stoploss_mock.call_count == 1 rc = client_delete(client, f"{BASE_URI}/trades/502") @@ -713,7 +704,7 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, markets=PropertyMock(return_value=markets), fetch_ticker=ticker, cancel_order=cancel_mock, @@ -731,15 +722,13 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short assert_response(rc, 502) assert 'No open order for trade_id' in rc.json()['error'] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc, 502) assert 'Order not found.' in rc.json()['error'] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=trade.orders[-1].to_ccxt_object()) + mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object()) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc) @@ -782,7 +771,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -844,7 +833,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -902,7 +891,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -945,7 +934,7 @@ def test_api_performance(botclient, fee): ) trade.close_profit = trade.calc_profit_ratio(trade.close_rate) trade.close_profit_abs = trade.calc_profit(trade.close_rate) - Trade.query.session.add(trade) + Trade.session.add(trade) trade = Trade( pair='XRP/ETH', @@ -962,7 +951,7 @@ def test_api_performance(botclient, fee): trade.close_profit = trade.calc_profit_ratio(trade.close_rate) trade.close_profit_abs = trade.calc_profit(trade.close_rate) - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() rc = client_get(client, f"{BASE_URI}/performance") @@ -983,7 +972,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1008,13 +997,15 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'close_profit_pct': None, 'close_profit_abs': None, 'close_rate': None, - 'current_profit': ANY, - 'current_profit_pct': ANY, - 'current_profit_abs': ANY, 'profit_ratio': ANY, 'profit_pct': ANY, 'profit_abs': ANY, 'profit_fiat': ANY, + 'total_profit_abs': ANY, + 'total_profit_fiat': ANY, + 'total_profit_ratio': ANY, + 'realized_profit': 0.0, + 'realized_profit_ratio': None, 'current_rate': current_rate, 'open_date': ANY, 'open_timestamp': ANY, @@ -1065,10 +1056,13 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'liquidation_price': None, 'funding_fees': None, 'trading_mode': ANY, + 'amount_precision': None, + 'price_precision': None, + 'precision_mode': None, 'orders': [ANY], } - mocker.patch('freqtrade.exchange.Exchange.get_rate', + mocker.patch(f'{EXMS}.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) rc = client_get(client, f"{BASE_URI}/status") @@ -1181,7 +1175,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): ftbot.config['force_entry_enable'] = True fbuy_mock = MagicMock(return_value=None) - mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) assert_response(rc) @@ -1207,7 +1201,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): strategy=CURRENT_TEST_STRATEGY, trading_mode=TradingMode.SPOT )) - mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) @@ -1244,6 +1238,8 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'profit_pct': None, 'profit_abs': None, 'profit_fiat': None, + 'realized_profit': 0.0, + 'realized_profit_ratio': None, 'fee_close': 0.0025, 'fee_close_cost': None, 'fee_close_currency': None, @@ -1268,6 +1264,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'liquidation_price': None, 'funding_fees': None, 'trading_mode': 'spot', + 'amount_precision': None, + 'price_precision': None, + 'precision_mode': None, 'orders': [], } @@ -1275,7 +1274,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): def test_api_forceexit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, @@ -1288,7 +1287,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): data={"tradeid": "1"}) assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"} - Trade.query.session.rollback() + Trade.rollback() create_mock_trades(fee) trade = Trade.get_trades([Trade.id == 5]).first() @@ -1297,7 +1296,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): data={"tradeid": "5", "ordertype": "market", "amount": 23}) assert_response(rc) assert rc.json() == {'result': 'Created sell order for trade 5.'} - Trade.query.session.rollback() + Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() assert pytest.approx(trade.amount) == 100 @@ -1307,7 +1306,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): data={"tradeid": "5"}) assert_response(rc) assert rc.json() == {'result': 'Created sell order for trade 5.'} - Trade.query.session.rollback() + Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() assert trade.is_open is False @@ -1630,7 +1629,7 @@ def test_sysinfo(botclient): def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): ftbot, client = botclient - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch(f'{EXMS}.get_fee', fee) rc = client_get(client, f"{BASE_URI}/backtest") # Backtest prevented in default mode @@ -1801,8 +1800,8 @@ def test_health(botclient): assert_response(rc) ret = rc.json() - assert ret['last_process_ts'] == 0 - assert ret['last_process'] == '1970-01-01T00:00:00+00:00' + assert ret["last_process_ts"] is None + assert ret["last_process"] is None def test_api_ws_subscribe(botclient, mocker): diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 855062af0..54f612c59 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -14,13 +14,15 @@ import arrow import pytest import time_machine from pandas import DataFrame +from sqlalchemy import select from telegram import Chat, Message, ReplyKeyboardMarkup, Update from telegram.error import BadRequest, NetworkError, TelegramError from freqtrade import __version__ from freqtrade.constants import CANCEL_REASON from freqtrade.edge import PairInfo -from freqtrade.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State +from freqtrade.enums import (ExitType, MarketDirection, RPCMessageType, RunMode, SignalDirection, + State) from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging @@ -29,9 +31,9 @@ from freqtrade.persistence.models import Order from freqtrade.rpc import RPC from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.telegram import Telegram, authorized_only -from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, create_mock_trades_usdt, - get_patched_freqtradebot, log_has, log_has_re, patch_exchange, - patch_get_signal, patch_whitelist) +from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, + create_mock_trades_usdt, get_patched_freqtradebot, log_has, log_has_re, + patch_exchange, patch_get_signal, patch_whitelist) class DummyCls(Telegram): @@ -106,7 +108,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " "['blacklist_delete', 'bl_delete'], " - "['logs'], ['edge'], ['health'], ['help'], ['version']" + "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir']" "]") assert log_has(message_str, caplog) @@ -197,11 +199,15 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'current_rate': 1.098e-05, 'amount': 90.99181074, 'stake_amount': 90.99181074, + 'max_stake_amount': 90.99181074, 'buy_tag': None, 'enter_tag': None, 'close_profit_ratio': None, 'profit': -0.0059, 'profit_ratio': -0.0059, + 'profit_abs': -0.225, + 'realized_profit': 0.0, + 'total_profit_abs': -0.225, 'initial_stop_loss_abs': 1.098e-05, 'stop_loss_abs': 1.099e-05, 'exit_order_status': None, @@ -236,7 +242,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_order=MagicMock(return_value=None), get_rate=MagicMock(return_value=0.22), ) @@ -275,6 +281,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: assert msg_mock.call_count == 4 msg = msg_mock.call_args_list[0][0][0] assert re.search(r'Number of Entries.*2', msg) + assert re.search(r'Number of Exits.*0', msg) assert re.search(r'Average Entry Price', msg) assert re.search(r'Order filled', msg) assert re.search(r'Close Date:', msg) is None @@ -288,7 +295,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_order=MagicMock(return_value=None), get_rate=MagicMock(return_value=0.22), ) @@ -296,8 +303,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) create_mock_trades(fee) - trades = Trade.get_trades([Trade.is_open.is_(False)]) - trade = trades[0] + trade = Trade.get_trades([Trade.is_open.is_(False)]).first() context = MagicMock() context.args = [str(trade.id)] telegram._status(update=update, context=context) @@ -310,7 +316,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 3 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -387,7 +393,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -432,7 +438,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -487,7 +493,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker ) @@ -521,7 +527,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -591,7 +597,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac return_value=1.1 ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -646,7 +652,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # The one-digit months should contain a zero, Eg: September 2021 = "2021-09" # Since we loaded the last 12 months, any month should appear - assert str('-09') in msg_mock.call_args_list[0][0][0] + assert '-09' in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -665,14 +671,15 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac context = MagicMock() context.args = ["february"] telegram._monthly(update=update, context=context) - assert str('Monthly Profit over the last 6 months:') in msg_mock.call_args_list[0][0][0] + assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0] -def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee, - limit_sell_order_usdt, mocker) -> None: +def test_telegram_profit_handle( + default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee, + limit_sell_order_usdt, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -687,7 +694,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f # Create some test data freqtradebot.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() context = MagicMock() # Test with invalid 2nd argument (should silently pass) @@ -702,8 +709,9 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f msg_mock.reset_mock() # Update the ticker with a market going up - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) + mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) # Simulate fulfilled LIMIT_SELL order for trade + trade = Trade.session.scalars(select(Trade)).first() oobj = Order.parse_from_ccxt_object( limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') trade.orders.append(oobj) @@ -735,7 +743,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -760,10 +768,9 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', - side_effect=lambda a, b: f"{a}/{b}") + mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) + mocker.patch(f'{EXMS}.get_tickers', tickers) + mocker.patch(f'{EXMS}.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -786,7 +793,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick def test_balance_handle_empty_response(default_conf, update, mocker) -> None: default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) + mocker.patch(f'{EXMS}.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -799,7 +806,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) + mocker.patch(f'{EXMS}.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -927,7 +934,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, patch_exchange(mocker) patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -941,11 +948,11 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, # Create some test data freqtradebot.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade # Increase the price and sell it - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) + mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) # /forceexit 1 context = MagicMock() @@ -996,7 +1003,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1012,11 +1019,11 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_sell_down ) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade # /forceexit 1 @@ -1067,7 +1074,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) patch_whitelist(mocker, default_conf) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1152,7 +1159,7 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 4 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -1206,7 +1213,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1223,7 +1230,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: # Reset and retry with specified price fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) # /forcelong ETH/BTC 0.055 context = MagicMock() context.args = ["ETH/BTC", "0.055"] @@ -1252,7 +1259,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1279,7 +1286,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1297,7 +1304,7 @@ def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, moc def test_telegram_entry_tag_performance_handle( default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1328,7 +1335,7 @@ def test_telegram_entry_tag_performance_handle( def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1359,7 +1366,7 @@ def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, tick def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1391,7 +1398,7 @@ def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1420,7 +1427,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, ) @@ -1488,7 +1495,7 @@ def test_whitelist_static(default_conf, update, mocker) -> None: def test_whitelist_dynamic(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 4 }] @@ -1682,7 +1689,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short): def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, ) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1703,8 +1710,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, msg_mock.reset_mock() trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=trade.orders[-1].to_ccxt_object()) + mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object()) context = MagicMock() context.args = [6] telegram._cancel_open_order(update=update, context=context) @@ -1726,14 +1732,14 @@ def test_version_handle(default_conf, update, mocker) -> None: telegram._version(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.strategy.version = lambda: '1.1.1' telegram._version(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0] assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0] @@ -2009,7 +2015,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'sub_trade': True, }) assert msg_mock.call_args[0][0] == ( - '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n' + '\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n' '*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n' '*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n' '*Enter Tag:* `buy_signal1`\n' @@ -2394,3 +2400,15 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: assert log_has("using custom keyboard from config.json: " "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', " "'/start', '/reload_config', '/help']]", caplog) + + +def test_change_market_direction(default_conf, mocker, update) -> None: + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE + context = MagicMock() + context.args = ["long"] + telegram._changemarketdir(update, context) + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG + context = MagicMock() + context.args = ["invalid"] + assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG diff --git a/tests/strategy/strats/broken_strats/broken_futures_strategies.py b/tests/strategy/strats/broken_strats/broken_futures_strategies.py index 7e6955d37..bb7ce2b32 100644 --- a/tests/strategy/strats/broken_strats/broken_futures_strategies.py +++ b/tests/strategy/strats/broken_strats/broken_futures_strategies.py @@ -7,6 +7,7 @@ from datetime import datetime from pandas import DataFrame +from freqtrade.persistence.trade_model import Order from freqtrade.strategy.interface import IStrategy @@ -35,7 +36,7 @@ class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return super().populate_exit_trend(dataframe, metadata) - def check_buy_timeout(self, pair: str, trade, order: dict, + def check_buy_timeout(self, pair: str, trade, order: Order, current_time: datetime, **kwargs) -> bool: return False @@ -44,6 +45,6 @@ class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return super().populate_exit_trend(dataframe, metadata) - def check_sell_timeout(self, pair: str, trade, order: dict, + def check_sell_timeout(self, pair: str, trade, order: Order, current_time: datetime, **kwargs) -> bool: return False diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index eadbc533f..d05e8ead2 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3): return prot bot_loop_started = False + bot_started = False def bot_loop_start(self): self.bot_loop_started = True @@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3): """ Parameters can also be defined here ... """ + self.bot_started = True self.buy_rsi = IntParameter([0, 50], default=30, space='buy') def informative_pairs(self): diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 6f5ff573b..2d5121403 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -197,7 +197,7 @@ class StrategyTestV3(IStrategy): if current_profit < -0.0075: orders = trade.select_filled_orders(trade.entry_side) - return round(orders[0].cost, 0) + return round(orders[0].safe_cost, 0) return None diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 0b30d2059..7b1399507 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -291,18 +291,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None: assert len(processed['UNITTEST/BTC']) == 103 -def test_populate_any_indicators(default_conf, testdatadir) -> None: - strategy = StrategyResolver.load_strategy(default_conf) - - timerange = TimeRange.parse_timerange('1510694220-1510700340') - data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, - fill_up_missing=True) - processed = strategy.populate_any_indicators('UNITTEST/BTC', data, '5m') - assert processed == data - assert id(processed) == id(data) - assert len(processed['UNITTEST/BTC']) == 103 - - def test_freqai_not_initialized(default_conf) -> None: strategy = StrategyResolver.load_strategy(default_conf) strategy.ft_bot_start() diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 36e997f7b..a55580780 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -169,6 +169,40 @@ def test_stoploss_from_open(side, profitrange): assert pytest.approx(stop_price) == expected_stop_price +@pytest.mark.parametrize("side,rel_stop,curr_profit,leverage,expected", [ + # profit range for long is [-1, inf] while for shorts is [-inf, 1] + ("long", 0, -1, 1, 1), + ("long", 0, 0.1, 1, 0.09090909), + ("long", -0.1, 0.1, 1, 0.18181818), + ("long", 0.1, 0.2, 1, 0.08333333), + ("long", 0.1, 0.5, 1, 0.266666666), + ("long", 0.1, 5, 1, 0.816666666), # 500% profit, set stoploss to 10% above open price + ("long", 0, 5, 10, 3.3333333), # 500% profit, set stoploss break even + ("long", 0.1, 5, 10, 3.26666666), # 500% profit, set stoploss to 10% above open price + ("long", -0.1, 5, 10, 3.3999999), # 500% profit, set stoploss to 10% belowopen price + + ("short", 0, 0.1, 1, 0.1111111), + ("short", -0.1, 0.1, 1, 0.2222222), + ("short", 0.1, 0.2, 1, 0.125), + ("short", 0.1, 1, 1, 1), + ("short", -0.01, 5, 10, 10.01999999), # 500% profit at 10x +]) +def test_stoploss_from_open_leverage(side, rel_stop, curr_profit, leverage, expected): + + stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short', leverage) + assert pytest.approx(stoploss) == expected + open_rate = 100 + if stoploss != 1: + if side == 'long': + current_rate = open_rate * (1 + curr_profit / leverage) + stop = current_rate * (1 - stoploss / leverage) + assert pytest.approx(stop) == open_rate * (1 + rel_stop / leverage) + else: + current_rate = open_rate * (1 - curr_profit / leverage) + stop = current_rate * (1 + stoploss / leverage) + assert pytest.approx(stop) == open_rate * (1 - rel_stop / leverage) + + def test_stoploss_from_absolute(): assert pytest.approx(stoploss_from_absolute(90, 100)) == 1 - (90 / 100) assert pytest.approx(stoploss_from_absolute(90, 100)) == 0.1 diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 98185e152..4cdb35936 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -69,7 +69,7 @@ def test_load_strategy(default_conf, dataframe_1m): def test_load_strategy_base64(dataframe_1m, caplog, default_conf): filepath = Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py' encoded_string = urlsafe_b64encode(filepath.read_bytes()).decode("utf-8") - default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)}) + default_conf.update({'strategy': f'SampleStrategy:{encoded_string}'}) strategy = StrategyResolver.load_strategy(default_conf) assert 'rsi' in strategy.advise_indicators(dataframe_1m, {'pair': 'ETH/BTC'}) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 4a94a3c2e..aab868bec 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -59,7 +59,7 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: def test_load_config_file(default_conf, mocker, caplog) -> None: del default_conf['user_data_dir'] default_conf['datadir'] = str(default_conf['datadir']) - file_mock = mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open( + file_mock = mocker.patch('freqtrade.configuration.load_config.Path.open', mocker.mock_open( read_data=json.dumps(default_conf) )) @@ -73,7 +73,8 @@ def test_load_config_file_error(default_conf, mocker, caplog) -> None: default_conf['datadir'] = str(default_conf['datadir']) filedata = json.dumps(default_conf).replace( '"stake_amount": 0.001,', '"stake_amount": .001,') - mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open(read_data=filedata)) + mocker.patch('freqtrade.configuration.load_config.Path.open', + mocker.mock_open(read_data=filedata)) mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata)) with pytest.raises(OperationalException, match=r".*Please verify the following segment.*"): @@ -272,7 +273,7 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> def test_load_config_file_exception(mocker) -> None: mocker.patch( - 'freqtrade.configuration.configuration.open', + 'freqtrade.configuration.configuration.Path.open', MagicMock(side_effect=FileNotFoundError('File not found')) ) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index dc4539401..01aa730cb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -10,6 +10,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock, patch import arrow import pytest from pandas import DataFrame +from sqlalchemy import select from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode, @@ -22,9 +23,10 @@ from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections.iprotection import ProtectionReturn from freqtrade.worker import Worker -from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, - get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, - patch_get_signal, patch_wallet, patch_whitelist) +from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, + get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, + patch_edge, patch_exchange, patch_get_signal, patch_wallet, + patch_whitelist) from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1, mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, mock_order_4, mock_order_5_stoploss, mock_order_6_sell) @@ -46,7 +48,7 @@ def patch_RPCManager(mocker) -> MagicMock: def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None: - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) assert freqtrade.state is State.RUNNING @@ -164,7 +166,7 @@ def test_check_available_stake_amount( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee @@ -234,7 +236,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker, 'last': enter_price, } mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value=ticker_val), get_fee=fee, ) @@ -246,7 +248,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() caplog.clear() ############################################# ticker_val.update({ @@ -269,7 +271,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) - patch_exchange(mocker) default_conf_usdt['max_open_trades'] = 2 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -277,7 +279,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) - freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade is not None assert trade.stake_amount == 60.0 @@ -285,7 +287,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) - assert trade.open_date is not None freqtrade.enter_positions() - trade = Trade.query.order_by(Trade.id.desc()).first() + trade = Trade.session.scalars(select(Trade).order_by(Trade.id.desc())).first() assert trade is not None assert trade.stake_amount == 60.0 @@ -304,7 +306,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -316,7 +318,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order, patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.create_trade('ETH/USDT') - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade is not None assert pytest.approx(trade.stake_amount) == 60.0 @@ -340,7 +342,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke patch_exchange(mocker) patch_wallet(mocker, free=default_conf_usdt['stake_amount'] * 0.5) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -366,7 +368,7 @@ def test_create_trade_minimal_amount( patch_exchange(mocker) enter_mock = MagicMock(return_value=limit_order_open[entry_side(is_short)]) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=enter_mock, get_fee=fee, @@ -401,7 +403,7 @@ def test_enter_positions_no_pairs_left(default_conf_usdt, ticker_usdt, limit_buy patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -428,7 +430,7 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}), get_fee=fee, @@ -479,7 +481,7 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_fee=fee, ) default_conf_usdt['stake_amount'] = 10 @@ -502,7 +504,7 @@ def test_create_trades_multiple_trades( default_conf_usdt['dry_run_wallet'] = 60.0 * max_open mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -524,7 +526,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, patch_exchange(mocker) default_conf_usdt['max_open_trades'] = 4 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -558,7 +560,7 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]), fetch_order=MagicMock(return_value=limit_order[entry_side(is_short)]), @@ -567,12 +569,12 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() assert not trades freqtrade.process() - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() assert len(trades) == 1 trade = trades[0] assert trade is not None @@ -594,7 +596,7 @@ def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> No patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=TemporaryError) ) @@ -611,7 +613,7 @@ def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) - msg_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=OperationalException) ) @@ -630,7 +632,7 @@ def test_process_trade_handling(default_conf_usdt, ticker_usdt, limit_buy_order_ patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), fetch_order=MagicMock(return_value=limit_buy_order_usdt_open), @@ -639,11 +641,11 @@ def test_process_trade_handling(default_conf_usdt, ticker_usdt, limit_buy_order_ freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade) - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() assert not trades freqtrade.process() - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() assert len(trades) == 1 # Nothing happened ... @@ -657,7 +659,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}), fetch_order=MagicMock(return_value=limit_buy_order_usdt), @@ -670,7 +672,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b assert pair not in default_conf_usdt['exchange']['pair_whitelist'] # create open trade not in whitelist - Trade.query.session.add(Trade( + Trade.session.add(Trade( pair=pair, stake_amount=0.001, fee_open=fee.return_value, @@ -680,7 +682,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b open_rate=0.01, exchange='binance', )) - Trade.query.session.add(Trade( + Trade.session.add(Trade( pair='ETH/USDT', stake_amount=0.001, fee_open=fee.return_value, @@ -705,7 +707,7 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) refresh_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=TemporaryError), refresh_latest_ohlcv=refresh_mock, @@ -785,7 +787,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, default_conf_usdt['exchange']['name'] = exchange_name if margin_mode: default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') + mocker.patch('freqtrade.exchange.gate.Gate.validate_ordertypes') patch_RPCManager(mocker) patch_exchange(mocker, id=exchange_name) freqtrade = FreqtradeBot(default_conf_usdt) @@ -796,7 +798,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, enter_rate_mock = MagicMock(return_value=bid) enter_mm = MagicMock(return_value=open_order) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=enter_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, @@ -813,7 +815,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, get_max_leverage=MagicMock(return_value=10), ) mocker.patch.multiple( - 'freqtrade.exchange.Okx', + 'freqtrade.exchange.okx.Okx', get_max_pair_stake_amount=MagicMock(return_value=500000), ) pair = 'ETH/USDT' @@ -837,7 +839,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, # Should create an open trade with an open order id # As the order is not fulfilled yet - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade assert trade.is_open is True @@ -862,10 +864,9 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['cost'] = 300 order['id'] = '444' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[2] + trade = Trade.session.scalars(select(Trade)).all()[2] trade.is_short = is_short assert trade assert trade.open_order_id is None @@ -881,10 +882,9 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['average'] = 0.5 order['cost'] = 10.0 order['id'] = '555' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert freqtrade.execute_entry(pair, stake_amount) - trade = Trade.query.all()[3] + trade = Trade.session.scalars(select(Trade)).all()[3] trade.is_short = is_short assert trade assert trade.open_order_id is None @@ -897,7 +897,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[4] + trade = Trade.session.scalars(select(Trade)).all()[4] trade.is_short = is_short assert trade assert pytest.approx(trade.stake_amount) == 150 @@ -906,7 +906,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['id'] = '557' freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[5] + trade = Trade.session.scalars(select(Trade)).all()[5] trade.is_short = is_short assert trade assert pytest.approx(trade.stake_amount) == 2.0 @@ -919,24 +919,23 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['average'] = 0.5 order['cost'] = 0.0 order['id'] = '66' - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=order)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) assert not freqtrade.execute_entry(pair, stake_amount) assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == 'spot' else 2 # Fail to get price... - mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) + mocker.patch(f'{EXMS}.get_rate', MagicMock(return_value=0.0)) with pytest.raises(PricingError, match="Could not determine entry price."): freqtrade.execute_entry(pair, stake_amount, is_short=is_short) # In case of custom entry price - mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) + mocker.patch(f'{EXMS}.get_rate', return_value=0.50) order['status'] = 'open' order['id'] = '5566' freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[6] + trade = Trade.session.scalars(select(Trade)).all()[6] trade.is_short = is_short assert trade assert trade.open_rate_requested == 0.508 @@ -948,12 +947,12 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, freqtrade.strategy.custom_entry_price = lambda **kwargs: None mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=MagicMock(return_value=10), ) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[7] + trade = Trade.session.scalars(select(Trade)).all()[7] trade.is_short = is_short assert trade assert trade.open_rate_requested == 10 @@ -963,7 +962,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['id'] = '5568' freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.all()[8] + trade = Trade.session.scalars(select(Trade)).all()[8] # Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False, # leverage=1.0, open_rate=10.00000000, open_since=...) # Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True, @@ -978,13 +977,13 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, order['id'] = '55672' mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_max_pair_stake_amount=MagicMock(return_value=500), ) freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500) assert freqtrade.execute_entry(pair, 2000, is_short=is_short) - trade = Trade.query.all()[9] + trade = Trade.session.scalars(select(Trade)).all()[9] trade.is_short = is_short assert pytest.approx(trade.stake_amount) == 500 @@ -993,7 +992,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, freqtrade.strategy.leverage.reset_mock() assert freqtrade.execute_entry(pair, 200, leverage_=3) assert freqtrade.strategy.leverage.call_count == 0 - trade = Trade.query.all()[10] + trade = Trade.session.scalars(select(Trade)).all()[10] assert trade.leverage == 1 if trading_mode == 'spot' else 3 @@ -1001,7 +1000,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1036,7 +1035,7 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, default_conf_usdt['margin_mode'] = 'isolated' freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1055,28 +1054,40 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, freqtrade.strategy.leverage = MagicMock(return_value=5.0) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.leverage == 5.0 # assert trade.stake_amount == 2 @pytest.mark.parametrize("is_short", [False, True]) -def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None: +def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(return_value=limit_order[entry_side(is_short)]), + get_fee=fee, + ) order = limit_order[entry_side(is_short)] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) stoploss = MagicMock(return_value={'id': 13434334}) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True - # TODO: should not be magicmock - trade = MagicMock() + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.open_order_id = None trade.stoploss_order_id = None @@ -1092,13 +1103,14 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho @pytest.mark.parametrize("is_short", [False, True]) def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short, limit_order) -> None: - stoploss = MagicMock(return_value={'id': 13434334}) + stop_order_dict = {'id': "13434334"} + stoploss = MagicMock(return_value=stop_order_dict) enter_order = limit_order[entry_side(is_short)] exit_order = limit_order[exit_side(is_short)] patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1117,8 +1129,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ # First case: when stoploss is not yet set but the order is open # should get the stoploss order id immediately # and should return false as no trade actually happened - # TODO: should not be magicmock - trade = MagicMock() + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True trade.open_order_id = None @@ -1130,44 +1143,62 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ # Second case: when stoploss is set but it is not yet hit # should do nothing and return false + stop_order_dict.update({'id': "102"}) trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = "100" + trade.stoploss_order_id = "102" + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='102', + status='open', + ) + ) hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order) + mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order) assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert trade.stoploss_order_id == "100" + assert trade.stoploss_order_id == "102" # Third case: when stoploss was set but it was canceled for some reason # should set a stoploss immediately and return False caplog.clear() trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = "100" + trade.stoploss_order_id = "102" - canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order) + canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'}) + mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order) stoploss.reset_mock() + amount_before = trade.amount + + stop_order_dict.update({'id': "103_1"}) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 1 - assert trade.stoploss_order_id == "13434334" + assert trade.stoploss_order_id == "103_1" + assert trade.amount == amount_before # Fourth case: when stoploss is set and it is hit # should unset stoploss_order_id and return true # as a trade actually happened caplog.clear() freqtrade.enter_positions() - trade = Trade.query.first() + stop_order_dict.update({'id': "104"}) + + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = "100" + trade.stoploss_order_id = "104" trade.orders.append(Order( ft_order_side='stoploss', - order_id='100', + order_id='104', ft_pair=trade.pair, ft_is_open=True, ft_amount=trade.amount, @@ -1176,24 +1207,21 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ assert trade stoploss_order_hit = MagicMock(return_value={ - 'id': "100", + 'id': "104", 'status': 'closed', 'type': 'stop_loss_limit', 'price': 3, 'average': 2, 'amount': enter_order['amount'], }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) assert trade.stoploss_order_id is None assert trade.is_open is False caplog.clear() - mocker.patch( - 'freqtrade.exchange.Exchange.create_stoploss', - side_effect=ExchangeError() - ) + mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) trade.is_open = True freqtrade.handle_stoploss_on_exchange(trade) assert log_has('Unable to place a stoploss order on exchange.', caplog) @@ -1201,11 +1229,11 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ # Fifth case: fetch_order returns InvalidOrder # It should try to add stoploss order - trade.stoploss_order_id = 100 + stop_order_dict.update({'id': "105"}) + trade.stoploss_order_id = "105" stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', - side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1214,33 +1242,48 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.stoploss_order_id = None trade.is_open = False stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.fetch_order') - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_order') + mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 0 # Seventh case: emergency exit triggered # Trailing stop should not act anymore stoploss_order_cancelled = MagicMock(side_effect=[{ - 'id': "100", + 'id': "107", 'status': 'canceled', 'type': 'stop_loss_limit', 'price': 3, 'average': 2, 'amount': enter_order['amount'], + 'filled': 0, + 'remaining': enter_order['amount'], 'info': {'stopPrice': 22}, }]) - trade.stoploss_order_id = 100 + trade.stoploss_order_id = "107" trade.is_open = True trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime trade.stop_loss = 24 + trade.exit_reason = None + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='107', + status='open', + ) + ) freqtrade.config['trailing_stop'] = True stoploss = MagicMock(side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', + Trade.commit() + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) - mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id is None assert trade.is_open is False @@ -1256,7 +1299,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1269,7 +1312,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), create_stoploss=MagicMock(side_effect=ExchangeError()), ) @@ -1277,11 +1320,22 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trade = Trade.query.first() - trade.is_short = is_short + trade = Trade.session.scalars(select(Trade)).first() + assert trade.is_short == is_short trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = 100 + trade.stoploss_order_id = "100" + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + status='open', + ) + ) assert trade assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -1303,7 +1357,7 @@ def test_create_stoploss_order_invalid_order( {'id': order['id']} ]) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1313,7 +1367,7 @@ def test_create_stoploss_order_invalid_order( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, fetch_order=MagicMock(return_value={'status': 'canceled'}), create_stoploss=MagicMock(side_effect=InvalidOrderException()), ) @@ -1322,7 +1376,7 @@ def test_create_stoploss_order_invalid_order( freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short caplog.clear() freqtrade.create_stoploss_order(trade, 200) @@ -1352,7 +1406,7 @@ def test_create_stoploss_order_insufficient_funds( mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1366,14 +1420,14 @@ def test_create_stoploss_order_insufficient_funds( fetch_order=MagicMock(return_value={'status': 'canceled'}), ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, create_stoploss=MagicMock(side_effect=InsufficientFundsError()), ) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short caplog.clear() freqtrade.create_stoploss_order(trade, 200) @@ -1400,10 +1454,10 @@ def test_handle_stoploss_on_exchange_trailing( # When trailing stoploss is set enter_order = limit_order[entry_side(is_short)] exit_order = limit_order[exit_side(is_short)] - stoploss = MagicMock(return_value={'id': 13434334}) + stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'}) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -1416,7 +1470,7 @@ def test_handle_stoploss_on_exchange_trailing( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1441,15 +1495,25 @@ def test_handle_stoploss_on_exchange_trailing( patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = 100 + trade.stoploss_order_id = '100' trade.stoploss_last_update = arrow.utcnow().shift(minutes=-20).datetime + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + ) + ) stoploss_order_hanging = MagicMock(return_value={ - 'id': 100, + 'id': '100', 'status': 'open', 'type': 'stop_loss_limit', 'price': hang_price, @@ -1459,7 +1523,7 @@ def test_handle_stoploss_on_exchange_trailing( } }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False @@ -1467,7 +1531,7 @@ def test_handle_stoploss_on_exchange_trailing( # price jumped 2x mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': bid[0], 'ask': ask[0], @@ -1476,9 +1540,9 @@ def test_handle_stoploss_on_exchange_trailing( ) cancel_order_mock = MagicMock() - stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1488,13 +1552,14 @@ def test_handle_stoploss_on_exchange_trailing( assert freqtrade.handle_trade(trade) is False assert trade.stop_loss == stop_price[1] + trade.stoploss_order_id = '100' # setting stoploss_on_exchange_interval to 0 seconds freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 assert freqtrade.handle_stoploss_on_exchange(trade) is False - cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') + cancel_order_mock.assert_called_once_with('100', 'ETH/USDT') stoploss_order_mock.assert_called_once_with( amount=pytest.approx(amt), pair='ETH/USDT', @@ -1506,7 +1571,7 @@ def test_handle_stoploss_on_exchange_trailing( # price fell below stoploss, so dry-run sells trade. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': bid[1], 'ask': ask[1], @@ -1524,11 +1589,11 @@ def test_handle_stoploss_on_exchange_trailing_error( enter_order = limit_order[entry_side(is_short)] exit_order = limit_order[exit_side(is_short)] # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': 13434334}) + stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1541,7 +1606,7 @@ def test_handle_stoploss_on_exchange_trailing_error( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1560,7 +1625,7 @@ def test_handle_stoploss_on_exchange_trailing_error( freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True trade.open_order_id = None @@ -1579,9 +1644,9 @@ def test_handle_stoploss_on_exchange_trailing_error( 'stopPrice': '0.1' } } - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', + mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) @@ -1592,8 +1657,8 @@ def test_handle_stoploss_on_exchange_trailing_error( # Fail creating stoploss order trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime caplog.clear() - cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) - mocker.patch("freqtrade.exchange.Binance.create_stoploss", side_effect=ExchangeError()) + cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') + mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 1 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) @@ -1603,14 +1668,14 @@ def test_stoploss_on_exchange_price_rounding( mocker, default_conf_usdt, fee, open_trade_usdt) -> None: patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_fee=fee, ) price_mock = MagicMock(side_effect=lambda p, s: int(s)) stoploss_mock = MagicMock(return_value={'id': '13434334'}) adjust_mock = MagicMock(return_value=False) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, create_stoploss=stoploss_mock, stoploss_adjust=adjust_mock, price_to_precision=price_mock, @@ -1633,10 +1698,10 @@ def test_handle_stoploss_on_exchange_custom_stop( enter_order = limit_order[entry_side(is_short)] exit_order = limit_order[exit_side(is_short)] # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': 13434334}) + stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'}) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -1649,7 +1714,7 @@ def test_handle_stoploss_on_exchange_custom_stop( get_fee=fee, ) mocker.patch.multiple( - 'freqtrade.exchange.Binance', + EXMS, create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1674,15 +1739,25 @@ def test_handle_stoploss_on_exchange_custom_stop( patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = 100 + trade.stoploss_order_id = '100' trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + ) + ) stoploss_order_hanging = MagicMock(return_value={ - 'id': 100, + 'id': '100', 'status': 'open', 'type': 'stop_loss_limit', 'price': 3, @@ -1692,14 +1767,14 @@ def test_handle_stoploss_on_exchange_custom_stop( } }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False # price jumped 2x mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.38 if not is_short else 1.9 / 2, 'ask': 4.4 if not is_short else 2.2 / 2, @@ -1708,9 +1783,10 @@ def test_handle_stoploss_on_exchange_custom_stop( ) cancel_order_mock = MagicMock() - stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) - mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) + trade.stoploss_order_id = '100' # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1727,7 +1803,7 @@ def test_handle_stoploss_on_exchange_custom_stop( assert freqtrade.handle_stoploss_on_exchange(trade) is False - cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') + cancel_order_mock.assert_called_once_with('100', 'ETH/USDT') # Long uses modified ask - offset, short modified bid + offset stoploss_order_mock.assert_called_once_with( amount=pytest.approx(trade.amount), @@ -1740,7 +1816,7 @@ def test_handle_stoploss_on_exchange_custom_stop( # price fell below stoploss, so dry-run sells trade. mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.17, 'ask': 4.19, @@ -1756,7 +1832,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde exit_order = limit_order['sell'] # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': 13434334}) + stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) patch_RPCManager(mocker) patch_exchange(mocker) patch_edge(mocker) @@ -1764,7 +1840,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde edge_conf['dry_run_wallet'] = 999.9 edge_conf['exchange']['name'] = 'binance' mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -1802,14 +1878,24 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = 100 - trade.stoploss_last_update = arrow.utcnow() + trade.stoploss_order_id = '100' + trade.stoploss_last_update = arrow.utcnow().datetime + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + ) + ) stoploss_order_hanging = MagicMock(return_value={ - 'id': 100, + 'id': '100', 'status': 'open', 'type': 'stop_loss_limit', 'price': 3, @@ -1817,7 +1903,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde 'stopPrice': '2.178' }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) # stoploss initially at 20% as edge dictated it. assert freqtrade.handle_trade(trade) is False @@ -1826,11 +1912,11 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) # price goes down 5% - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.19 * 0.95, 'ask': 2.2 * 0.95, 'last': 2.19 * 0.95 @@ -1845,7 +1931,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde cancel_order_mock.assert_not_called() # price jumped 2x - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 4.38, 'ask': 4.4, 'last': 4.38 @@ -1856,7 +1942,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde # stoploss should be set to 1% as trailing is on assert trade.stop_loss == 4.4 * 0.99 - cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') + cancel_order_mock.assert_called_once_with('100', 'NEO/BTC') stoploss_order_mock.assert_called_once_with( amount=pytest.approx(11.41438356), pair='NEO/BTC', @@ -1895,9 +1981,8 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=limit_order[entry_side(is_short)]) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=limit_order[entry_side(is_short)]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) # TODO: should not be magicmock trade = MagicMock() @@ -1921,7 +2006,7 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) order = limit_order[entry_side(is_short)] - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) # TODO: should not be magicmock trade = MagicMock() @@ -1947,8 +2032,8 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca order = limit_order[entry_side(is_short)] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.0) order_id = order['id'] @@ -1999,7 +2084,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca limit_buy_order_usdt_new['status'] = 'canceled' mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt_new) + mocker.patch(f'{EXMS}.fetch_order', return_value=limit_buy_order_usdt_new) res = freqtrade.update_trade_state(trade, order_id) # Cancelled empty assert res is True @@ -2018,9 +2103,9 @@ def test_update_trade_state_withorderdict( trades_for_order[0]['amount'] = initial_amount order_id = "oid_123456" order['id'] = order_id - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) # fetch_order should not be called!! - mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError)) patch_exchange(mocker) amount = sum(x['amount'] for x in trades_for_order) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -2062,7 +2147,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit caplog) -> None: order = limit_order[entry_side(is_short)] freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) # TODO: should not be magicmock trade = MagicMock() @@ -2080,8 +2165,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(side_effect=InvalidOrderException)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=InvalidOrderException)) # TODO: should not be magicmock trade = MagicMock() @@ -2101,9 +2185,9 @@ def test_update_trade_state_sell( buy_order = limit_order[entry_side(is_short)] open_order = limit_order_open[exit_side(is_short)] l_order = limit_order[exit_side(is_short)] - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) # fetch_order should not be called!! - mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(side_effect=ValueError)) wallet_mock = MagicMock() mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock) @@ -2153,7 +2237,7 @@ def test_handle_trade( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -2170,7 +2254,7 @@ def test_handle_trade( freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade @@ -2206,7 +2290,7 @@ def test_handle_overlapping_signals( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ open_order, @@ -2225,7 +2309,7 @@ def test_handle_overlapping_signals( freqtrade.enter_positions() # Buy and Sell triggering, so doing nothing ... - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() nb_trades = len(trades) assert nb_trades == 0 @@ -2233,7 +2317,7 @@ def test_handle_overlapping_signals( # Buy is triggering, so buying ... patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() for trade in trades: trade.is_short = is_short nb_trades = len(trades) @@ -2243,7 +2327,7 @@ def test_handle_overlapping_signals( # Buy and Sell are not triggering, so doing nothing ... patch_get_signal(freqtrade, enter_long=False) assert freqtrade.handle_trade(trades[0]) is False - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() for trade in trades: trade.is_short = is_short nb_trades = len(trades) @@ -2256,7 +2340,7 @@ def test_handle_overlapping_signals( else: patch_get_signal(freqtrade, enter_long=True, exit_long=True) assert freqtrade.handle_trade(trades[0]) is False - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() for trade in trades: trade.is_short = is_short nb_trades = len(trades) @@ -2268,7 +2352,7 @@ def test_handle_overlapping_signals( patch_get_signal(freqtrade, enter_long=False, exit_short=True) else: patch_get_signal(freqtrade, enter_long=False, exit_long=True) - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() for trade in trades: trade.is_short = is_short assert freqtrade.handle_trade(trades[0]) is True @@ -2284,7 +2368,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ open_order, @@ -2299,7 +2383,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True @@ -2327,7 +2411,7 @@ def test_handle_trade_use_exit_signal( caplog.set_level(logging.DEBUG) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=[ enter_open_order, @@ -2341,7 +2425,7 @@ def test_handle_trade_use_exit_signal( freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True @@ -2367,7 +2451,7 @@ def test_close_trade( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=open_order), get_fee=fee, @@ -2378,7 +2462,7 @@ def test_close_trade( # Create trade and sell it freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade @@ -2424,7 +2508,7 @@ def test_manage_open_orders_entry_usercustom( patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order=cancel_order_mock, @@ -2435,7 +2519,7 @@ def test_manage_open_orders_entry_usercustom( open_trade.is_short = is_short open_trade.orders[0].side = 'sell' if is_short else 'buy' open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy' - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # Ensure default is to return empty (so not mocked yet) @@ -2446,7 +2530,8 @@ def test_manage_open_orders_entry_usercustom( freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 0 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() nb_trades = len(trades) assert nb_trades == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 1 @@ -2454,7 +2539,8 @@ def test_manage_open_orders_entry_usercustom( freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 0 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() nb_trades = len(trades) assert nb_trades == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 1 @@ -2464,7 +2550,8 @@ def test_manage_open_orders_entry_usercustom( freqtrade.manage_open_orders() assert cancel_order_wr_mock.call_count == 1 assert rpc_mock.call_count == 2 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() nb_trades = len(trades) assert nb_trades == 0 assert freqtrade.strategy.check_entry_timeout.call_count == 1 @@ -2485,7 +2572,7 @@ def test_manage_open_orders_entry( cancel_order_mock = MagicMock(return_value=limit_buy_cancel) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2494,7 +2581,7 @@ def test_manage_open_orders_entry( freqtrade = FreqtradeBot(default_conf_usdt) open_trade.is_short = is_short - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) @@ -2503,7 +2590,8 @@ def test_manage_open_orders_entry( freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 2 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() nb_trades = len(trades) assert nb_trades == 0 # Custom user buy-timeout is never called @@ -2524,7 +2612,7 @@ def test_adjust_entry_cancel( limit_buy_cancel['status'] = 'canceled' cancel_order_mock = MagicMock(return_value=limit_buy_cancel) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2532,7 +2620,7 @@ def test_adjust_entry_cancel( ) open_trade.is_short = is_short - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # Timeout to not interfere @@ -2541,9 +2629,10 @@ def test_adjust_entry_cancel( # check that order is cancelled freqtrade.strategy.adjust_entry_price = MagicMock(return_value=None) freqtrade.manage_open_orders() - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 0 - assert len(Order.query.all()) == 0 + assert len(Order.session.scalars(select(Order)).all()) == 0 assert log_has_re( f"{'Sell' if is_short else 'Buy'} order user requested order cancel*", caplog) assert log_has_re( @@ -2565,7 +2654,7 @@ def test_adjust_entry_maintain_replace( limit_buy_cancel['status'] = 'canceled' cancel_order_mock = MagicMock(return_value=limit_buy_cancel) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order_with_result=cancel_order_mock, @@ -2573,7 +2662,7 @@ def test_adjust_entry_maintain_replace( ) open_trade.is_short = is_short - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # Timeout to not interfere @@ -2582,7 +2671,8 @@ def test_adjust_entry_maintain_replace( # Check that order is maintained freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order['price']) freqtrade.manage_open_orders() - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 1 assert len(Order.get_open_orders()) == 1 # Entry adjustment is called @@ -2592,9 +2682,10 @@ def test_adjust_entry_maintain_replace( freqtrade.get_valid_enter_price_and_stake = MagicMock(return_value={100, 10, 1}) freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234) freqtrade.manage_open_orders() - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 1 - nb_all_orders = len(Order.query.all()) + nb_all_orders = len(Order.session.scalars(select(Order)).all()) assert nb_all_orders == 2 # New order seems to be in closed status? # nb_open_orders = len(Order.get_open_orders()) @@ -2617,7 +2708,7 @@ def test_check_handle_cancelled_buy( patch_exchange(mocker) old_order.update({"status": "canceled", 'filled': 0.0}) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=old_order), cancel_order=cancel_order_mock, @@ -2626,14 +2717,15 @@ def test_check_handle_cancelled_buy( freqtrade = FreqtradeBot(default_conf_usdt) open_trade.orders = [] open_trade.is_short = is_short - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # check it does cancel buy orders over the time limit freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 2 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 0 assert log_has_re( f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog) @@ -2647,7 +2739,7 @@ def test_manage_open_orders_buy_exception( cancel_order_mock = MagicMock() patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, validate_pairs=MagicMock(), fetch_ticker=ticker_usdt, fetch_order=MagicMock(side_effect=ExchangeError), @@ -2657,14 +2749,15 @@ def test_manage_open_orders_buy_exception( freqtrade = FreqtradeBot(default_conf_usdt) open_trade.is_short = is_short - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # check it does cancel buy orders over the time limit freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 1 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() nb_trades = len(trades) assert nb_trades == 1 @@ -2685,10 +2778,10 @@ def test_manage_open_orders_exit_usercustom( rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock() patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.0) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.0) et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order=cancel_order_mock @@ -2699,7 +2792,7 @@ def test_manage_open_orders_exit_usercustom( open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_profit_abs = 0.001 - Trade.query.session.add(open_trade_usdt) + Trade.session.add(open_trade_usdt) Trade.commit() # Ensure default is false freqtrade.manage_open_orders() @@ -2732,21 +2825,21 @@ def test_manage_open_orders_exit_usercustom( assert freqtrade.strategy.check_exit_timeout.call_count == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 0 - # 2nd canceled trade - Fail execute sell + # 2nd canceled trade - Fail execute exit caplog.clear() open_trade_usdt.open_order_id = limit_sell_order_old['id'] mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit', side_effect=DependencyException) freqtrade.manage_open_orders() - assert log_has_re('Unable to emergency sell .*', caplog) + assert log_has_re('Unable to emergency exit .*', caplog) et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit') caplog.clear() # 2nd canceled trade ... open_trade_usdt.open_order_id = limit_sell_order_old['id'] - # If cancelling fails - no emergency sell! + # If cancelling fails - no emergency exit! with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False): freqtrade.manage_open_orders() assert et_mock.call_count == 0 @@ -2766,7 +2859,7 @@ def test_manage_open_orders_exit( limit_sell_order_old['side'] = 'buy' if is_short else 'sell' patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order=cancel_order_mock, @@ -2779,7 +2872,7 @@ def test_manage_open_orders_exit( open_trade_usdt.close_profit_abs = 0.001 open_trade_usdt.is_short = is_short - Trade.query.session.add(open_trade_usdt) + Trade.session.add(open_trade_usdt) Trade.commit() freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False) @@ -2808,7 +2901,7 @@ def test_check_handle_cancelled_exit( patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_sell_order_old), cancel_order_with_result=cancel_order_mock @@ -2819,7 +2912,7 @@ def test_check_handle_cancelled_exit( open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.is_short = is_short - Trade.query.session.add(open_trade_usdt) + Trade.session.add(open_trade_usdt) Trade.commit() # check it does cancel sell orders over the time limit @@ -2849,14 +2942,14 @@ def test_manage_open_orders_partial( cancel_order_mock = MagicMock(return_value=limit_buy_canceled) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock ) freqtrade = FreqtradeBot(default_conf_usdt) prior_stake = open_trade.stake_amount - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # check it does cancel buy orders over the time limit @@ -2864,7 +2957,8 @@ def test_manage_open_orders_partial( freqtrade.manage_open_orders() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 3 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 1 assert trades[0].amount == 23.0 assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage @@ -2889,7 +2983,7 @@ def test_manage_open_orders_partial_fee( mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0)) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock, @@ -2901,7 +2995,7 @@ def test_manage_open_orders_partial_fee( open_trade.fee_open = fee() open_trade.fee_close = fee() - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # cancelling a half-filled order should update the amount to the bought amount # and apply fees if necessary. @@ -2911,7 +3005,8 @@ def test_manage_open_orders_partial_fee( assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 3 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 1 # Verify that trade has been updated assert trades[0].amount == (limit_buy_order_old_partial['amount'] - @@ -2937,7 +3032,7 @@ def test_manage_open_orders_partial_except( cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(return_value=limit_buy_order_old_partial), cancel_order_with_result=cancel_order_mock, @@ -2951,7 +3046,7 @@ def test_manage_open_orders_partial_except( open_trade.fee_open = fee() open_trade.fee_close = fee() - Trade.query.session.add(open_trade) + Trade.session.add(open_trade) Trade.commit() # cancelling a half-filled order should update the amount to the bought amount # and apply fees if necessary. @@ -2961,7 +3056,8 @@ def test_manage_open_orders_partial_except( assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 3 - trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + trades = Trade.session.scalars( + select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all() assert len(trades) == 1 # Verify that trade has been updated @@ -2983,14 +3079,14 @@ def test_manage_open_orders_exception(default_conf_usdt, ticker_usdt, open_trade handle_cancel_exit=MagicMock(), ) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, fetch_order=MagicMock(side_effect=ExchangeError('Oh snap')), cancel_order=cancel_order_mock ) freqtrade = FreqtradeBot(default_conf_usdt) - Trade.query.session.add(open_trade_usdt) + Trade.session.add(open_trade_usdt) Trade.commit() caplog.clear() @@ -3013,13 +3109,13 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ del cancel_buy_order['filled'] cancel_order_mock = MagicMock(return_value=cancel_buy_order) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) + mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade._notify_enter_cancel = MagicMock() trade = mock_trade_usdt_4(fee, is_short) - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() l_order['filled'] = 0.0 @@ -3044,11 +3140,12 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ # Order remained open for some reason (cancel failed) cancel_buy_order['status'] = 'open' cancel_order_mock = MagicMock(return_value=cancel_buy_order) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) + trade.open_order_id = 'some_open_order' + mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock) assert not freqtrade.handle_cancel_enter(trade, l_order, reason) assert log_has_re(r"Order .* for .* not cancelled.", caplog) # min_pair_stake empty should not crash - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=None) assert not freqtrade.handle_cancel_enter(trade, limit_order[entry_side(is_short)], reason) @@ -3060,15 +3157,15 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho patch_RPCManager(mocker) patch_exchange(mocker) cancel_order_mock = mocker.patch( - 'freqtrade.exchange.Exchange.cancel_order_with_result', + f'{EXMS}.cancel_order_with_result', return_value=limit_buy_order_canceled_empty) - nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel') + notify_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel') freqtrade = FreqtradeBot(default_conf_usdt) reason = CANCEL_REASON['TIMEOUT'] trade = mock_trade_usdt_4(fee, is_short) - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert cancel_order_mock.call_count == 0 @@ -3077,7 +3174,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho r'Removing .* from database\.', caplog ) - assert nofiy_mock.call_count == 1 + assert notify_mock.call_count == 1 @pytest.mark.parametrize("is_short", [False, True]) @@ -3094,7 +3191,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order l_order = limit_order[entry_side(is_short)] cancel_order_mock = MagicMock(return_value=cancelorder) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, cancel_order=cancel_order_mock, fetch_order=MagicMock(side_effect=InvalidOrderException) ) @@ -3102,7 +3199,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order freqtrade = FreqtradeBot(default_conf_usdt) freqtrade._notify_enter_cancel = MagicMock() trade = mock_trade_usdt_4(fee, is_short) - Trade.query.session.add(trade) + Trade.session.add(trade) Trade.commit() l_order['filled'] = 0.0 l_order['status'] = 'open' @@ -3114,7 +3211,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order l_order['filled'] = 1.0 order = deepcopy(l_order) order['status'] = 'canceled' - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) assert not freqtrade.handle_cancel_enter(trade, l_order, reason) assert cancel_order_mock.call_count == 1 @@ -3124,11 +3221,11 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: patch_exchange(mocker) cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, cancel_order=cancel_order_mock, ) - mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.245441) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.2) + mocker.patch(f'{EXMS}.get_rate', return_value=0.245441) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.2) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee') @@ -3230,9 +3327,8 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=0.0) - mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', - side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.0) + mocker.patch(f'{EXMS}.cancel_order_with_result', side_effect=InvalidOrderException()) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3240,6 +3336,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: trade = MagicMock() reason = CANCEL_REASON['TIMEOUT'] order = {'remaining': 1, + 'id': '125', 'amount': 1, 'status': "open"} assert not freqtrade.handle_cancel_exit(trade, order, reason) @@ -3254,7 +3351,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3268,14 +3365,14 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ freqtrade.enter_positions() rpc_mock.reset_mock() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short assert trade assert freqtrade.strategy.confirm_trade_exit.call_count == 0 # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up ) # Prevented sell ... @@ -3320,6 +3417,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ 'profit_ratio': 0.00493809 if is_short else 0.09451372, 'stake_currency': 'USDT', 'fiat_currency': 'USD', + 'base_currency': 'ETH', 'sell_reason': ExitType.ROI.value, 'exit_reason': ExitType.ROI.value, 'open_date': ANY, @@ -3337,7 +3435,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3349,13 +3447,13 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down ) freqtrade.execute_trade_exit( @@ -3383,6 +3481,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd 'profit_amount': -5.65990099 if is_short else -0.00075, 'profit_ratio': -0.0945681 if is_short else -1.247e-05, 'stake_currency': 'USDT', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value, @@ -3406,7 +3505,7 @@ def test_execute_trade_exit_custom_exit_price( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3422,14 +3521,14 @@ def test_execute_trade_exit_custom_exit_price( freqtrade.enter_positions() rpc_mock.reset_mock() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade assert freqtrade.strategy.confirm_trade_exit.call_count == 0 # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3468,6 +3567,7 @@ def test_execute_trade_exit_custom_exit_price( 'profit_amount': pytest.approx(profit_amount), 'profit_ratio': profit_ratio, 'stake_currency': 'USDT', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'sell_reason': 'foo', 'exit_reason': 'foo', @@ -3487,7 +3587,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=False), @@ -3499,13 +3599,13 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short assert trade # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down ) @@ -3541,6 +3641,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( 'profit_ratio': -0.00501253 if is_short else -0.01493766, 'stake_currency': 'USDT', 'fiat_currency': 'USD', + 'base_currency': 'ETH', 'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value, 'open_date': ANY, @@ -3555,8 +3656,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( def test_execute_trade_exit_sloe_cancel_exception( mocker, default_conf_usdt, ticker_usdt, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) create_order_mock = MagicMock(side_effect=[ {'id': '12345554'}, @@ -3564,7 +3664,7 @@ def test_execute_trade_exit_sloe_cancel_exception( ]) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, create_order=create_order_mock, @@ -3574,7 +3674,7 @@ def test_execute_trade_exit_sloe_cancel_exception( patch_get_signal(freqtrade) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() PairLock.session = MagicMock() freqtrade.config['dry_run'] = False @@ -3583,7 +3683,7 @@ def test_execute_trade_exit_sloe_cancel_exception( freqtrade.execute_trade_exit(trade=trade, limit=1234, exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) assert create_order_mock.call_count == 2 - assert log_has('Could not cancel stoploss order abcd', caplog) + assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog) @pytest.mark.parametrize("is_short", [False, True]) @@ -3595,14 +3695,16 @@ def test_execute_trade_exit_with_stoploss_on_exchange( patch_exchange(mocker) stoploss = MagicMock(return_value={ 'id': 123, + 'status': 'open', 'info': { 'foo': 'bar' } }) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee') cancel_order = MagicMock(return_value=True) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -3619,7 +3721,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade trades = [trade] @@ -3629,7 +3731,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3639,7 +3741,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS) ) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade assert cancel_order.call_count == 1 @@ -3653,7 +3755,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -3668,7 +3770,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( } }) - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -3677,7 +3779,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( # Create some test data freqtrade.enter_positions() freqtrade.manage_open_orders() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trades = [trade] assert trade.stoploss_order_id is None @@ -3696,18 +3798,18 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( "lastTradeTimestamp": None, "symbol": "BTC/USDT", "type": "stop_loss_limit", - "side": "sell", + "side": "buy" if is_short else "sell", "price": 1.08801, - "amount": 90.99181074, - "cost": 99.0000000032274, + "amount": trade.amount, + "cost": 1.08801 * trade.amount, "average": 1.08801, - "filled": 90.99181074, + "filled": trade.amount, "remaining": 0.0, "status": "closed", "fee": None, "trades": None }) - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_executed) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed) freqtrade.exit_positions(trades) assert trade.stoploss_order_id is None @@ -3750,7 +3852,7 @@ def test_execute_trade_exit_market_order( rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), @@ -3763,13 +3865,13 @@ def test_execute_trade_exit_market_order( # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up, _dry_is_price_crossed=MagicMock(return_value=False), ) @@ -3806,6 +3908,7 @@ def test_execute_trade_exit_market_order( 'profit_amount': pytest.approx(profit_amount), 'profit_ratio': profit_ratio, 'stake_currency': 'USDT', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'sell_reason': ExitType.ROI.value, 'exit_reason': ExitType.ROI.value, @@ -3825,7 +3928,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, create_order=MagicMock(side_effect=[ @@ -3838,13 +3941,13 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_up ) @@ -3879,7 +3982,7 @@ def test_exit_profit_only( patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': bid, 'ask': ask, @@ -3906,7 +4009,7 @@ def test_exit_profit_only( exit_type=ExitType.NONE)) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) trade.update_order(limit_order[eside]) @@ -3930,7 +4033,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 0.00002172, 'ask': 0.00002173, @@ -3949,7 +4052,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() amnt = trade.amount oobj = Order.parse_from_ccxt_object(limit_order['buy'], limit_order['buy']['symbol'], 'buy') @@ -4007,7 +4110,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -4017,13 +4120,13 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, # Create some test data freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short assert trade # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt_sell_down ) @@ -4052,7 +4155,7 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_ patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.19, 'ask': 2.2, @@ -4072,7 +4175,7 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_ freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short oobj = Order.parse_from_ccxt_object( limit_order[eside], limit_order[eside]['symbol'], eside) @@ -4103,7 +4206,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.0, 'ask': 2.0, @@ -4122,12 +4225,12 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short assert freqtrade.handle_trade(trade) is False # Raise praise into profits - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.0 * val1, 'ask': 2.0 * val1, @@ -4138,7 +4241,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, assert freqtrade.handle_trade(trade) is False caplog.clear() # Price fell - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': 2.0 * val2, 'ask': 2.0 * val2, @@ -4173,7 +4276,7 @@ def test_trailing_stop_loss_positive( patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': enter_price - (-0.01 if is_short else 0.01), 'ask': enter_price - (-0.01 if is_short else 0.01), @@ -4197,7 +4300,7 @@ def test_trailing_stop_loss_positive( freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) trade.update_order(limit_order[eside]) @@ -4208,7 +4311,7 @@ def test_trailing_stop_loss_positive( # Raise ticker_usdt above buy price mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.06 if is_short else 0.06), 'ask': enter_price + (-0.06 if is_short else 0.06), @@ -4230,7 +4333,7 @@ def test_trailing_stop_loss_positive( caplog.clear() mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.135 if is_short else 0.125), 'ask': enter_price + (-0.135 if is_short else 0.125), @@ -4246,7 +4349,7 @@ def test_trailing_stop_loss_positive( assert log_has("ETH/USDT - Adjusting stoploss...", caplog) mocker.patch( - 'freqtrade.exchange.Exchange.fetch_ticker', + f'{EXMS}.fetch_ticker', MagicMock(return_value={ 'bid': enter_price + (-0.02 if is_short else 0.02), 'ask': enter_price + (-0.02 if is_short else 0.02), @@ -4271,7 +4374,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi patch_exchange(mocker) eside = entry_side(is_short) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 2.0, 'ask': 2.0, @@ -4294,7 +4397,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short oobj = Order.parse_from_ccxt_object( @@ -4312,7 +4415,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -4338,7 +4441,7 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) walletmock = mocker.patch('freqtrade.wallets.Wallets.update') mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=8.1122) amount = sum(x['amount'] for x in trades_for_order) @@ -4363,7 +4466,7 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee): - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) amount = buy_order_fee['amount'] trade = Trade( @@ -4417,7 +4520,7 @@ def test_get_real_amount( buy_order['fee'] = fee_par trades_for_order[0]['fee'] = fee_par - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -4431,7 +4534,7 @@ def test_get_real_amount( freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) if not use_ticker_usdt_rate: - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_ticker', side_effect=ExchangeError) caplog.clear() order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') @@ -4463,7 +4566,7 @@ def test_get_real_amount_multi( if fee_currency: trades_for_order[0]['fee']['currency'] = fee_currency - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) default_conf_usdt['stake_currency'] = "ETH" @@ -4480,8 +4583,8 @@ def test_get_real_amount_multi( # Fake markets entry to enable fee parsing markets['BNB/ETH'] = markets['ETH/USDT'] freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': 0.19, 'last': 0.2}) # Amount is reduced by "fee" @@ -4510,7 +4613,7 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ limit_buy_order_usdt = deepcopy(buy_order_fee) limit_buy_order_usdt['fee'] = {'cost': 0.004} - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4531,9 +4634,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, fee, mocker): - tfo_mock = mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) - mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', return_value='BNB/USDT') - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 200}) + tfo_mock = mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) + mocker.patch(f'{EXMS}.get_valid_pair_combination', return_value='BNB/USDT') + mocker.patch(f'{EXMS}.fetch_ticker', return_value={'last': 200}) trade = Trade( pair='LTC/USDT', amount=30.0, @@ -4559,7 +4662,7 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o limit_buy_order_usdt = deepcopy(buy_order_fee) limit_buy_order_usdt['amount'] = limit_buy_order_usdt['amount'] - 0.001 - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4584,7 +4687,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord limit_buy_order_usdt = deepcopy(buy_order_fee) trades_for_order[0]['amount'] = trades_for_order[0]['amount'] + 1e-15 - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades_for_order) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -4664,7 +4767,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker, ] }] - mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=trades) amount = float(sum(x['amount'] for x in trades)) trade = Trade( pair='CEL/USDT', @@ -4746,9 +4849,9 @@ def test_order_book_depth_of_market( default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = delta patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]), get_fee=fee, @@ -4760,7 +4863,7 @@ def test_order_book_depth_of_market( patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() if is_high_delta: assert trade is None else: @@ -4771,7 +4874,7 @@ def test_order_book_depth_of_market( assert trade.open_date is not None assert trade.exchange == 'binance' - assert len(Trade.query.all()) == 1 + assert len(Trade.session.scalars(select(Trade)).all()) == 1 # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object( @@ -4794,7 +4897,7 @@ def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exc patch_exchange(mocker) ticker_usdt_mock = MagicMock(return_value={'ask': ask, 'last': last}) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_l2_order_book=MagicMock(return_value=order_book) if order_book else order_book_l2, fetch_ticker=ticker_usdt_mock, ) @@ -4822,7 +4925,7 @@ def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None """ patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_l2_order_book=order_book_l2 ) default_conf_usdt['telegram']['enabled'] = False @@ -4843,7 +4946,7 @@ def test_order_book_exit_pricing( """ test order book ask strategy """ - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) + mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) default_conf_usdt['exchange']['name'] = 'binance' default_conf_usdt['exit_pricing']['use_order_book'] = True default_conf_usdt['exit_pricing']['order_book_top'] = 1 @@ -4851,7 +4954,7 @@ def test_order_book_exit_pricing( patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, 'ask': 2.2, @@ -4868,7 +4971,7 @@ def test_order_book_exit_pricing( freqtrade.enter_positions() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade time.sleep(0.01) # Race condition fix @@ -4884,8 +4987,7 @@ def test_order_book_exit_pricing( assert freqtrade.handle_trade(trade) is True assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] - mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', - return_value={'bids': [[]], 'asks': [[]]}) + mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) with pytest.raises(PricingError): freqtrade.handle_trade(trade) assert log_has_re( @@ -4897,14 +4999,14 @@ def test_startup_state(default_conf_usdt, mocker): default_conf_usdt['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} } - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) worker = get_patched_worker(mocker, default_conf_usdt) assert worker.freqtrade.state is State.RUNNING def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) reinit_mock = MagicMock() mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock) @@ -4929,7 +5031,7 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_ default_conf_usdt['tradable_balance_ratio'] = 1.0 patch_exchange(mocker) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, @@ -4941,7 +5043,7 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_ n = bot.enter_positions() assert n == 2 - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == 2 bot.config['max_open_trades'] = 3 @@ -4961,7 +5063,7 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim is_short, buy_calls, sell_calls): default_conf_usdt['cancel_open_orders_on_exit'] = True mocker.patch( - 'freqtrade.exchange.Exchange.fetch_order', + f'{EXMS}.fetch_order', side_effect=[ ExchangeError(), limit_order[exit_side(is_short)], @@ -4974,7 +5076,7 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) create_mock_trades(fee, is_short=is_short) - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == MOCK_TRADE_COUNT freqtrade.cancel_all_open_orders() assert buy_mock.call_count == buy_calls @@ -4990,7 +5092,7 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short): assert freqtrade.rpc.send_msg.call_count == 0 create_mock_trades(fee, is_short) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True @@ -5017,17 +5119,17 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s matching_buy_order.update({ 'status': 'closed', }) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=matching_buy_order) + mocker.patch(f'{EXMS}.fetch_order', return_value=matching_buy_order) freqtrade.startup_update_open_orders() # Only stoploss and sell orders are kept open assert len(Order.get_open_orders()) == 2 caplog.clear() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=ExchangeError) + mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) freqtrade.startup_update_open_orders() assert log_has_re(r"Error updating Order .*", caplog) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=InvalidOrderException) + mocker.patch(f'{EXMS}.fetch_order', side_effect=InvalidOrderException) hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_order') # Orders which are no longer found after X days should be assumed as canceled. freqtrade.startup_update_open_orders() @@ -5072,7 +5174,7 @@ def test_update_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_ 'currency': order['symbol'].split('/')[0]}}) return order - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', side_effect=[ patch_with_fee(mock_order_2_sell(is_short=is_short)), patch_with_fee(mock_order_3_sell(is_short=is_short)), @@ -5132,8 +5234,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - return_value={'status': 'open'}) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', return_value={'status': 'open'}) create_mock_trades(fee, is_short) trades = Trade.get_trades().all() @@ -5159,7 +5260,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh exchange='binance', is_short=is_short ) - Trade.query.session.add(trade) + Trade.session.add(trade) freqtrade.handle_insufficient_funds(trade) # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) @@ -5173,7 +5274,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') - mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mock_fo = mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', return_value={'status': 'open'}) def reset_open_orders(trade): @@ -5259,7 +5360,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap caplog.clear() # Test error case - mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mock_fo = mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', side_effect=ExchangeError()) order = mock_order_5_stoploss(is_short=is_short) @@ -5439,11 +5540,10 @@ def test_update_funding_fees( return ret - mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', - side_effect=refresh_latest_ohlcv_mock) + mocker.patch(f'{EXMS}.refresh_latest_ohlcv', side_effect=refresh_latest_ohlcv_mock) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=enter_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 1.9, @@ -5467,7 +5567,7 @@ def test_update_funding_fees( assert len(trades) == 3 for trade in trades: assert pytest.approx(trade.funding_fees) == 0 - mocker.patch('freqtrade.exchange.Exchange.create_order', return_value=open_exit_order) + mocker.patch(f'{EXMS}.create_order', return_value=open_exit_order) time_machine.move_to("2021-09-01 08:00:00 +00:00") if schedule_off: for trade in trades: @@ -5497,7 +5597,7 @@ def test_update_funding_fees( def test_update_funding_fees_error(mocker, default_conf, caplog): - mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', side_effect=ExchangeError()) + mocker.patch(f'{EXMS}.get_funding_fees', side_effect=ExchangeError()) default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' freqtrade = get_patched_freqtradebot(mocker, default_conf) @@ -5522,7 +5622,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: stake_amount = 10 buy_rate_mock = MagicMock(return_value=bid) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -5551,17 +5651,16 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '650', 'order_id': '650' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_buy_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_buy_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_buy_order)) assert freqtrade.execute_entry(pair, stake_amount) # Should create an closed trade with an no open order id # Order is filled and trade is open - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 1 - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5571,7 +5670,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: # Assume it does nothing since order is closed and trade is open freqtrade.update_trades_without_assigned_fees() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5581,7 +5680,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: freqtrade.manage_open_orders() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5603,16 +5702,14 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '651', 'order_id': '651' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=open_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=open_dca_order_1)) + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=open_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=open_dca_order_1)) assert freqtrade.execute_entry(pair, stake_amount, trade=trade) - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 2 - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id == '651' assert trade.open_rate == 11 @@ -5637,19 +5734,19 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: # Assume it does nothing since order is still open fetch_order_mm = MagicMock(side_effect=make_sure_its_651) - mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm) + mocker.patch(f'{EXMS}.create_order', fetch_order_mm) + mocker.patch(f'{EXMS}.fetch_order', fetch_order_mm) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', fetch_order_mm) freqtrade.update_trades_without_assigned_fees() - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 2 # Assert that the trade is found as open and without fees trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 # Assert trade is as expected - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id == '651' assert trade.open_rate == 11 @@ -5679,23 +5776,21 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'datetime': arrow.utcnow().isoformat(), } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_dca_order_1)) freqtrade.manage_open_orders() # Assert trade is as expected (averaged dca) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert pytest.approx(trade.open_rate) == 9.90909090909 assert trade.amount == 22 assert pytest.approx(trade.stake_amount) == 218 - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 2 @@ -5723,23 +5818,21 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '652', 'order_id': '652' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_dca_order_2)) assert freqtrade.execute_entry(pair, stake_amount, trade=trade) # Assert trade is as expected (averaged dca) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert pytest.approx(trade.open_rate) == 8.729729729729 assert trade.amount == 37 assert trade.stake_amount == 323 - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 3 @@ -5761,18 +5854,16 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: 'id': '653', 'order_id': '653' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_1)) assert freqtrade.execute_trade_exit(trade=trade, limit=8, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=15) # Assert trade is as expected (averaged dca) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert trade.is_open @@ -5780,7 +5871,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert trade.stake_amount == 192.05405405405406 assert pytest.approx(trade.open_rate) == 8.729729729729 - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 4 @@ -5811,7 +5902,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: amount = 100 buy_rate_mock = MagicMock(return_value=bid) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -5839,17 +5930,16 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '600', 'order_id': '600' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_buy_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_buy_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_buy_order)) assert freqtrade.execute_entry(pair, amount) # Should create an closed trade with an no open order id # Order is filled and trade is open - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 1 - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5859,7 +5949,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: # Assume it does nothing since order is closed and trade is open freqtrade.update_trades_without_assigned_fees() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5869,7 +5959,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: freqtrade.manage_open_orders() - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.is_open is True assert trade.open_order_id is None @@ -5894,11 +5984,9 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '601', 'order_id': '601' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_1)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_1)) assert freqtrade.execute_trade_exit(trade=trade, limit=ask, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), @@ -5907,7 +5995,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert len(trades) == 1 # Assert trade is as expected (averaged dca) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert trade.amount == 50 @@ -5916,7 +6004,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert pytest.approx(trade.realized_profit) == -152.375 assert pytest.approx(trade.close_profit_abs) == -152.375 - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 2 # Make sure the closed order is found as the second order. @@ -5940,18 +6028,16 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'id': '602', 'order_id': '602' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_sell_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=closed_sell_dca_order_2)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order', MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_2)) assert freqtrade.execute_trade_exit(trade=trade, limit=ask, exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount) # Assert trade is as expected (averaged dca) - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert trade.amount == 50 @@ -5960,7 +6046,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: # Trade fully realized assert pytest.approx(trade.realized_profit) == 94.25 assert pytest.approx(trade.close_profit_abs) == 94.25 - orders = Order.query.all() + orders = Order.session.scalars(select(Order)).all() assert orders assert len(orders) == 3 @@ -6007,7 +6093,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: price = order[2] price_mock = MagicMock(return_value=price) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=price_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, @@ -6034,9 +6120,8 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: 'id': f'60{idx}', 'order_id': f'60{idx}' } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=closed_successful_order)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_successful_order)) + mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_order)) if order[0] == 'buy': assert freqtrade.execute_entry(pair, amount, trade=trade) @@ -6046,11 +6131,11 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount) - orders1 = Order.query.all() + orders1 = Order.session.scalars(select(Order)).all() assert orders1 assert len(orders1) == idx + 1 - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade if idx < len(data) - 1: assert trade.is_open is True @@ -6065,7 +6150,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None: order_obj = trade.select_order(order[0], False) assert order_obj.order_id == f'60{idx}' - trade = Trade.query.first() + trade = Trade.session.scalars(select(Trade)).first() assert trade assert trade.open_order_id is None assert trade.is_open is False @@ -6094,7 +6179,7 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) buy_rate_mock = MagicMock(return_value=10) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_rate=buy_rate_mock, fetch_ticker=MagicMock(return_value={ 'bid': 10, diff --git a/tests/test_integration.py b/tests/test_integration.py index 4d8b282c9..9fb9fd8b3 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,12 +1,13 @@ from unittest.mock import MagicMock import pytest +from sqlalchemy import select from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode from freqtrade.persistence import Trade from freqtrade.persistence.models import Order from freqtrade.rpc.rpc import RPC -from tests.conftest import get_patched_freqtradebot, log_has_re, patch_get_signal +from tests.conftest import EXMS, get_patched_freqtradebot, log_has_re, patch_get_signal def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, @@ -34,7 +35,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, "type": "stop_loss_limit", "side": "sell", "price": 1.08801, - "amount": 90.99181074, + "amount": 91.07468123, "cost": 0.0, "average": 0.0, "filled": 0.0, @@ -48,17 +49,18 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, stoploss_order_closed['filled'] = stoploss_order_closed['amount'] # Sell first trade based on stoploss, keep 2nd and 3rd trade open + stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open] stoploss_order_mock = MagicMock( - side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open]) + side_effect=stop_orders) # Sell 3rd trade (not called for the first trade) should_sell_mock = MagicMock(side_effect=[ [], [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] ) cancel_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, + create_stoploss=stoploss, fetch_ticker=ticker, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -91,14 +93,15 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, assert freqtrade.strategy.confirm_trade_exit.call_count == 0 wallets_mock.reset_mock() - trades = Trade.query.all() - # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state) - for trade in trades: - stoploss_order_closed['id'] = '3' - oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss') + trades = Trade.session.scalars(select(Trade)).all() + # Make sure stoploss-order is open and trade is bought + for idx, trade in enumerate(trades): + stop_order = stop_orders[idx] + stop_order['id'] = f"stop{idx}" + oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss') trade.orders.append(oobj) - trade.stoploss_order_id = '3' + trade.stoploss_order_id = f"stop{idx}" trade.open_order_id = None n = freqtrade.exit_positions(trades) @@ -147,7 +150,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati default_conf['telegram']['enabled'] = True mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, get_fee=fee, amount_to_precision=lambda s, x, y: y, @@ -179,13 +182,13 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati n = freqtrade.enter_positions() assert n == 4 - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == 4 assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1 rpc._rpc_force_entry('TKN/BTC', None) - trades = Trade.query.all() + trades = Trade.session.scalars(select(Trade)).all() assert len(trades) == 5 for trade in trades: @@ -217,7 +220,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, ) @@ -239,7 +242,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995 - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -286,7 +289,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: round(y, 4), @@ -311,7 +314,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004 - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif) # additional buy order freqtrade.process() @@ -361,16 +364,16 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, ) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) - mocker.patch("freqtrade.exchange.Exchange.get_funding_fees", return_value=0) - mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", return_value=(0, 0)) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) + mocker.patch(f"{EXMS}.get_funding_fees", return_value=0) + mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0, 0)) patch_get_signal(freqtrade) freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96 @@ -385,12 +388,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert trade.open_order_id is not None assert pytest.approx(trade.stake_amount) == 60 assert trade.open_rate == 1.96 - assert trade.stop_loss_pct is None - assert trade.stop_loss == 0.0 + assert trade.stop_loss_pct == -0.1 + assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage) + assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage) + assert trade.initial_stop_loss_pct == -0.1 assert trade.leverage == leverage assert trade.stake_amount == 60 - assert trade.initial_stop_loss == 0.0 - assert trade.initial_stop_loss_pct is None # No adjustment freqtrade.process() trade = Trade.get_trades().first() @@ -406,14 +409,14 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert trade.open_order_id is not None # Open rate is not adjusted yet assert trade.open_rate == 1.96 - assert trade.stop_loss_pct is None - assert trade.stop_loss == 0.0 + assert trade.stop_loss_pct == -0.1 + assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage) + assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage) assert trade.stake_amount == 60 - assert trade.initial_stop_loss == 0.0 - assert trade.initial_stop_loss_pct is None + assert trade.initial_stop_loss_pct == -0.1 # Fill order - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 @@ -423,12 +426,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert pytest.approx(trade.stake_amount) == 60 assert trade.stop_loss_pct == -0.1 assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage) - assert pytest.approx(trade.initial_stop_loss) == 1.99 * (1 - 0.1 / leverage) + assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage) assert trade.initial_stop_loss_pct == -0.1 # 2nd order - not filling freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) freqtrade.process() trade = Trade.get_trades().first() @@ -452,7 +455,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # Fill DCA order freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None) - mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) + mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError) freqtrade.process() @@ -477,14 +480,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade.trading_mode = TradingMode.FUTURES mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker_usdt, get_fee=fee, amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, get_min_pair_stake_amount=MagicMock(return_value=10), ) - mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) + mocker.patch(f"{EXMS}.get_max_leverage", return_value=10) patch_get_signal(freqtrade) freqtrade.strategy.leverage = MagicMock(return_value=leverage) @@ -532,8 +535,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert trade.is_open # use amount that would trunc to 0.0 once selling - mocker.patch("freqtrade.exchange.Exchange.amount_to_contract_precision", - lambda s, p, v: round(v, 1)) + mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1)) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01) freqtrade.process() trade = Trade.get_trades().first() diff --git a/tests/test_misc.py b/tests/test_misc.py index 596c7bd51..6b4343ab2 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -46,7 +46,7 @@ def test_shorten_date() -> None: def test_file_dump_json(mocker) -> None: - file_open = mocker.patch('freqtrade.misc.open', MagicMock()) + file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock()) json_dump = mocker.patch('rapidjson.dump', MagicMock()) file_dump_json(Path('somefile'), [1, 2, 3]) assert file_open.call_count == 1 diff --git a/tests/test_strategy_updater.py b/tests/test_strategy_updater.py new file mode 100644 index 000000000..597d49fda --- /dev/null +++ b/tests/test_strategy_updater.py @@ -0,0 +1,214 @@ +# pragma pylint: disable=missing-docstring, protected-access, invalid-name + +import re +import shutil +import sys +from pathlib import Path + +import pytest + +from freqtrade.commands.strategy_utils_commands import start_strategy_update +from freqtrade.strategy.strategyupdater import StrategyUpdater +from tests.conftest import get_args + + +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: + # Effective test without mocks. + teststrats = Path(__file__).parent / 'strategy/strats' + tmpdirp = Path(tmpdir) / "strategies" + tmpdirp.mkdir() + shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp) + old_code = (teststrats / 'strategy_test_v2.py').read_text() + + args = [ + "strategy-updater", + "--userdir", + str(tmpdir), + "--strategy-list", + "StrategyTestV2" + ] + pargs = get_args(args) + pargs['config'] = None + + start_strategy_update(pargs) + + assert Path(tmpdir / "strategies_orig_updater").exists() + # Backup file exists + assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists() + # updated file exists + new_file = Path(tmpdirp / 'strategy_test_v2.py') + assert new_file.exists() + new_code = new_file.read_text() + assert 'INTERFACE_VERSION = 3' in new_code + assert 'INTERFACE_VERSION = 2' in old_code + captured = capsys.readouterr() + + assert 'Conversion of strategy_test_v2.py started.' in captured.out + assert re.search(r'Conversion of strategy_test_v2\.py took .* seconds', captured.out) + + +def test_strategy_updater_methods(default_conf, caplog) -> None: + + instance_strategy_updater = StrategyUpdater() + modified_code1 = instance_strategy_updater.update_code(""" +class testClass(IStrategy): + def populate_buy_trend(): + pass + def populate_sell_trend(): + pass + def check_buy_timeout(): + pass + def check_sell_timeout(): + pass + def custom_sell(): + pass +""") + + assert "populate_entry_trend" in modified_code1 + assert "populate_exit_trend" in modified_code1 + assert "check_entry_timeout" in modified_code1 + assert "check_exit_timeout" in modified_code1 + assert "custom_exit" in modified_code1 + assert "INTERFACE_VERSION = 3" in modified_code1 + + +def test_strategy_updater_params(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + + modified_code2 = instance_strategy_updater.update_code(""" +ticker_interval = '15m' +buy_some_parameter = IntParameter(space='buy') +sell_some_parameter = IntParameter(space='sell') +""") + + assert "timeframe" in modified_code2 + # check for not editing hyperopt spaces + assert "space='buy'" in modified_code2 + assert "space='sell'" in modified_code2 + + +def test_strategy_updater_constants(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code3 = instance_strategy_updater.update_code(""" +use_sell_signal = True +sell_profit_only = True +sell_profit_offset = True +ignore_roi_if_buy_signal = True +forcebuy_enable = True +""") + + assert "use_exit_signal" in modified_code3 + assert "exit_profit_only" in modified_code3 + assert "exit_profit_offset" in modified_code3 + assert "ignore_roi_if_entry_signal" in modified_code3 + assert "force_entry_enable" in modified_code3 + + +def test_strategy_updater_df_columns(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code = instance_strategy_updater.update_code(""" +dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1") +dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1 +""") + + assert "enter_long" in modified_code + assert "exit_long" in modified_code + assert "enter_tag" in modified_code + + +def test_strategy_updater_method_params(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code = instance_strategy_updater.update_code(""" +def confirm_trade_exit(sell_reason: str): + nr_orders = trade.nr_of_successful_buys + pass + """) + assert "exit_reason" in modified_code + assert "nr_orders = trade.nr_of_successful_entries" in modified_code + + +def test_strategy_updater_dicts(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code = instance_strategy_updater.update_code(""" +order_time_in_force = { + 'buy': 'gtc', + 'sell': 'ioc' +} +order_types = { + 'buy': 'limit', + 'sell': 'market', + 'stoploss': 'market', + 'stoploss_on_exchange': False +} +unfilledtimeout = { + 'buy': 1, + 'sell': 2 +} +""") + + assert "'entry': 'gtc'" in modified_code + assert "'exit': 'ioc'" in modified_code + assert "'entry': 'limit'" in modified_code + assert "'exit': 'market'" in modified_code + assert "'entry': 1" in modified_code + assert "'exit': 2" in modified_code + + +def test_strategy_updater_comparisons(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code = instance_strategy_updater.update_code(""" +def confirm_trade_exit(sell_reason): + if (sell_reason == 'stop_loss'): + pass +""") + assert "exit_reason" in modified_code + assert "exit_reason == 'stop_loss'" in modified_code + + +def test_strategy_updater_strings(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + + modified_code = instance_strategy_updater.update_code(""" +sell_reason == 'sell_signal' +sell_reason == 'force_sell' +sell_reason == 'emergency_sell' +""") + + # those tests currently don't work, next in line. + assert "exit_signal" in modified_code + assert "exit_reason" in modified_code + assert "force_exit" in modified_code + assert "emergency_exit" in modified_code + + +def test_strategy_updater_comments(default_conf, caplog) -> None: + instance_strategy_updater = StrategyUpdater() + modified_code = instance_strategy_updater.update_code(""" +# This is the 1st comment +import talib.abstract as ta +# This is the 2nd comment +import freqtrade.vendor.qtpylib.indicators as qtpylib + + +class someStrategy(IStrategy): + INTERFACE_VERSION = 2 + # This is the 3rd comment + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "0": 0.50 + } + + # This is the 4th comment + stoploss = -0.1 +""") + + assert "This is the 1st comment" in modified_code + assert "This is the 2nd comment" in modified_code + assert "This is the 3rd comment" in modified_code + assert "INTERFACE_VERSION = 3" in modified_code + # currently still missing: + # Webhook terminology, Telegram notification settings, Strategy/Config settings diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 61e8f279d..7ccc8d0f5 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -6,13 +6,13 @@ import pytest from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import DependencyException -from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_wallet +from tests.conftest import EXMS, create_mock_trades, get_patched_freqtradebot, patch_wallet def test_sync_wallet_at_boot(mocker, default_conf): default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.0, @@ -45,7 +45,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert 'USDT' in freqtrade.wallets._wallets assert freqtrade.wallets._last_wallet_refresh > 0 mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.2, @@ -87,7 +87,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): def test_sync_wallet_missing_data(mocker, default_conf): default_conf['dry_run'] = False mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "BNT": { "free": 1.0, @@ -136,7 +136,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r result1, result2, limit_buy_order_open, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, fetch_ticker=ticker, create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee @@ -312,7 +312,7 @@ def test_sync_wallet_futures_live(mocker, default_conf): } ] mocker.patch.multiple( - 'freqtrade.exchange.Exchange', + EXMS, get_balances=MagicMock(return_value={ "USDT": { "free": 900, diff --git a/tests/test_worker.py b/tests/test_worker.py index 88d495e13..79e2f35d4 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -8,11 +8,11 @@ import time_machine from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import State from freqtrade.worker import Worker -from tests.conftest import get_patched_worker, log_has, log_has_re +from tests.conftest import EXMS, get_patched_worker, log_has, log_has_re def test_worker_state(mocker, default_conf, markets) -> None: - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) worker = get_patched_worker(mocker, default_conf) assert worker.freqtrade.state is State.RUNNING diff --git a/tests/testdata/XRP_ETH-trades.feather b/tests/testdata/XRP_ETH-trades.feather new file mode 100644 index 000000000..68e1c8467 Binary files /dev/null and b/tests/testdata/XRP_ETH-trades.feather differ diff --git a/user_data/strategies/.gitkeep b/user_data/strategies/.gitkeep deleted file mode 100644 index e69de29bb..000000000