diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5c80bc141..21d6f9ef4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -57,7 +57,7 @@ jobs:
- name: Installation - *nix
if: runner.os == 'Linux'
run: |
- python -m pip install --upgrade pip wheel
+ python -m pip install --upgrade pip==23.0.1 wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -163,7 +163,7 @@ jobs:
rm /usr/local/bin/python3.11-config || true
brew install hdf5 c-blosc
- python -m pip install --upgrade pip wheel
+ python -m pip install --upgrade pip==23.0.1 wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -352,7 +352,7 @@ jobs:
- name: Installation - *nix
if: runner.os == 'Linux'
run: |
- python -m pip install --upgrade pip wheel
+ python -m pip install --upgrade pip==23.0.1 wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -425,7 +425,7 @@ jobs:
python setup.py sdist bdist_wheel
- name: Publish to PyPI (Test)
- uses: pypa/gh-action-pypi-publish@v1.8.3
+ uses: pypa/gh-action-pypi-publish@v1.8.5
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.8.3
+ uses: pypa/gh-action-pypi-publish@v1.8.5
if: (github.event_name == 'release')
with:
user: __token__
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4784055a9..89370eacc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,12 +13,12 @@ repos:
- id: mypy
exclude: build_helpers
additional_dependencies:
- - types-cachetools==5.3.0.4
+ - types-cachetools==5.3.0.5
- types-filelock==3.2.7
- - types-requests==2.28.11.16
- - types-tabulate==0.9.0.1
- - types-python-dateutil==2.8.19.10
- - SQLAlchemy==2.0.7
+ - types-requests==2.28.11.17
+ - types-tabulate==0.9.0.2
+ - types-python-dateutil==2.8.19.12
+ - SQLAlchemy==2.0.9
# stages: [push]
- repo: https://github.com/pycqa/isort
diff --git a/Dockerfile b/Dockerfile
index 6a4a168c1..422caecaf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.10.10-slim-bullseye as base
+FROM python:3.10.11-slim-bullseye as base
# Setup env
ENV LANG C.UTF-8
@@ -25,7 +25,7 @@ FROM base as python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
- && pip install --upgrade pip
+ && pip install --upgrade pip==23.0.1
# Install TA-lib
COPY build_helpers/* /tmp/
diff --git a/README.md b/README.md
index c8bc50dac..57c4e3a52 100644
--- a/README.md
+++ b/README.md
@@ -210,6 +210,6 @@ To run this bot we recommend you a cloud instance with a minimum of:
- [Python >= 3.8](http://docs.python-guide.org/en/latest/starting/installation/)
- [pip](https://pip.pypa.io/en/stable/installing/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
-- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
+- [TA-Lib](https://ta-lib.github.io/ta-lib-python/)
- [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
- [Docker](https://www.docker.com/products/docker) (Recommended)
diff --git a/build_helpers/TA_Lib-0.4.25-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.25-cp310-cp310-win_amd64.whl
deleted file mode 100644
index c6435da0d..000000000
Binary files a/build_helpers/TA_Lib-0.4.25-cp310-cp310-win_amd64.whl and /dev/null differ
diff --git a/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl b/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl
deleted file mode 100644
index 4d55d18c8..000000000
Binary files a/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl and /dev/null differ
diff --git a/build_helpers/TA_Lib-0.4.25-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.25-cp38-cp38-win_amd64.whl
deleted file mode 100644
index f2806db80..000000000
Binary files a/build_helpers/TA_Lib-0.4.25-cp38-cp38-win_amd64.whl and /dev/null differ
diff --git a/build_helpers/TA_Lib-0.4.25-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.25-cp39-cp39-win_amd64.whl
deleted file mode 100644
index 0d4ceb3b4..000000000
Binary files a/build_helpers/TA_Lib-0.4.25-cp39-cp39-win_amd64.whl and /dev/null differ
diff --git a/build_helpers/TA_Lib-0.4.26-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.26-cp310-cp310-win_amd64.whl
new file mode 100644
index 000000000..466455a8c
Binary files /dev/null and b/build_helpers/TA_Lib-0.4.26-cp310-cp310-win_amd64.whl differ
diff --git a/build_helpers/TA_Lib-0.4.26-cp311-cp311-win_amd64.whl b/build_helpers/TA_Lib-0.4.26-cp311-cp311-win_amd64.whl
new file mode 100644
index 000000000..16f4f411a
Binary files /dev/null and b/build_helpers/TA_Lib-0.4.26-cp311-cp311-win_amd64.whl differ
diff --git a/build_helpers/TA_Lib-0.4.26-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.26-cp38-cp38-win_amd64.whl
new file mode 100644
index 000000000..324502fb8
Binary files /dev/null and b/build_helpers/TA_Lib-0.4.26-cp38-cp38-win_amd64.whl differ
diff --git a/build_helpers/TA_Lib-0.4.26-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.26-cp39-cp39-win_amd64.whl
new file mode 100644
index 000000000..389403041
Binary files /dev/null and b/build_helpers/TA_Lib-0.4.26-cp39-cp39-win_amd64.whl differ
diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1
index 36606cc3a..cf6fbdf07 100644
--- a/build_helpers/install_windows.ps1
+++ b/build_helpers/install_windows.ps1
@@ -1,21 +1,21 @@
# Downloads don't work automatically, since the URL is regenerated via javascript.
# Downloaded from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib
-python -m pip install --upgrade pip wheel
+python -m pip install --upgrade pip==23.0.1 wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.8') {
- pip install build_helpers\TA_Lib-0.4.25-cp38-cp38-win_amd64.whl
+ pip install build_helpers\TA_Lib-0.4.26-cp38-cp38-win_amd64.whl
}
if ($pyv -eq '3.9') {
- pip install build_helpers\TA_Lib-0.4.25-cp39-cp39-win_amd64.whl
+ pip install build_helpers\TA_Lib-0.4.26-cp39-cp39-win_amd64.whl
}
if ($pyv -eq '3.10') {
- pip install build_helpers\TA_Lib-0.4.25-cp310-cp310-win_amd64.whl
+ pip install build_helpers\TA_Lib-0.4.26-cp310-cp310-win_amd64.whl
}
if ($pyv -eq '3.11') {
- pip install build_helpers\TA_Lib-0.4.25-cp311-cp311-win_amd64.whl
+ pip install build_helpers\TA_Lib-0.4.26-cp311-cp311-win_amd64.whl
}
pip install -r requirements-dev.txt
pip install -e .
diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh
index a6ecdbee6..8f0de2cc9 100755
--- a/build_helpers/publish_docker_arm64.sh
+++ b/build_helpers/publish_docker_arm64.sh
@@ -12,6 +12,7 @@ TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
TAG_PLOT=${TAG}_plot
TAG_FREQAI=${TAG}_freqai
TAG_FREQAI_RL=${TAG_FREQAI}rl
+TAG_FREQAI_TORCH=${TAG_FREQAI}torch
TAG_PI="${TAG}_pi"
TAG_ARM=${TAG}_arm
@@ -42,9 +43,9 @@ if [ $? -ne 0 ]; then
return 1
fi
-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 .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_FREQAI_ARM} -f docker/Dockerfile.freqai .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG_FREQAI_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
@@ -84,6 +85,10 @@ 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}
+# Create special Torch tag - which is identical to the RL tag.
+docker manifest create ${IMAGE_NAME}:${TAG_FREQAI_TORCH} ${CACHE_IMAGE}:${TAG_FREQAI_RL} ${CACHE_IMAGE}:${TAG_FREQAI_RL_ARM}
+docker manifest push -p ${IMAGE_NAME}:${TAG_FREQAI_TORCH}
+
# copy images to ghcr.io
alias crane="docker run --rm -i -v $(pwd)/.crane:/home/nonroot/.docker/ gcr.io/go-containerregistry/crane"
@@ -93,6 +98,7 @@ 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_RL} ${GHCR_IMAGE_NAME}:${TAG_FREQAI_TORCH}
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}
diff --git a/build_helpers/publish_docker_multi.sh b/build_helpers/publish_docker_multi.sh
index 27fa06b95..72b20ac5d 100755
--- a/build_helpers/publish_docker_multi.sh
+++ b/build_helpers/publish_docker_multi.sh
@@ -58,9 +58,9 @@ fi
# Tag image for upload and next build step
docker tag freqtrade:$TAG ${CACHE_IMAGE}:$TAG
-docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
-docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG} -t freqtrade:${TAG_FREQAI} -f docker/Dockerfile.freqai .
-docker build --cache-from freqtrade:${TAG_FREQAI} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_FREQAI} -t freqtrade:${TAG_FREQAI_RL} -f docker/Dockerfile.freqai_rl .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG} -t freqtrade:${TAG_FREQAI} -f docker/Dockerfile.freqai .
+docker build --build-arg sourceimage=freqtrade --build-arg sourcetag=${TAG_FREQAI} -t freqtrade:${TAG_FREQAI_RL} -f docker/Dockerfile.freqai_rl .
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
docker tag freqtrade:$TAG_FREQAI ${CACHE_IMAGE}:$TAG_FREQAI
diff --git a/docs/assets/freqai_pytorch-diagram.png b/docs/assets/freqai_pytorch-diagram.png
new file mode 100644
index 000000000..f48ebae25
Binary files /dev/null and b/docs/assets/freqai_pytorch-diagram.png differ
diff --git a/docs/backtesting.md b/docs/backtesting.md
index 0227df3f6..166c2b28b 100644
--- a/docs/backtesting.md
+++ b/docs/backtesting.md
@@ -274,19 +274,20 @@ A backtesting result will look like that:
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
-========================================================= EXIT REASON STATS ==========================================================
-| Exit Reason | Exits | Wins | Draws | Losses |
-|:-------------------|--------:|------:|-------:|--------:|
-| trailing_stop_loss | 205 | 150 | 0 | 55 |
-| stop_loss | 166 | 0 | 0 | 166 |
-| exit_signal | 56 | 36 | 0 | 20 |
-| force_exit | 2 | 0 | 0 | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
+==================== EXIT REASON STATS ====================
+| Exit Reason | Exits | Wins | Draws | Losses |
+|:-------------------|--------:|------:|-------:|--------:|
+| trailing_stop_loss | 205 | 150 | 0 | 55 |
+| stop_loss | 166 | 0 | 0 | 166 |
+| exit_signal | 56 | 36 | 0 | 20 |
+| force_exit | 2 | 0 | 0 | 2 |
+
================== SUMMARY METRICS ==================
| Metric | Value |
|-----------------------------+---------------------|
diff --git a/docs/freqai-configuration.md b/docs/freqai-configuration.md
index 886dc2338..233edf2c5 100644
--- a/docs/freqai-configuration.md
+++ b/docs/freqai-configuration.md
@@ -236,3 +236,161 @@ If you want to predict multiple targets you must specify all labels in the same
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
df['&s-up_or_down'] = np.where( df["close"].shift(-100) == df["close"], 'same', df['&s-up_or_down'])
```
+
+## PyTorch Module
+
+### Quick start
+
+The easiest way to quickly run a pytorch model is with the following command (for regression task):
+
+```bash
+freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel PyTorchMLPRegressor --strategy-path freqtrade/templates
+```
+
+!!! note "Installation/docker"
+ The PyTorch module requires large packages such as `torch`, which should be explicitly requested during `./setup.sh -i` by answering "y" to the question "Do you also want dependencies for freqai-rl or PyTorch (~700mb additional space required) [y/N]?".
+ Users who prefer docker should ensure they use the docker image appended with `_freqaitorch`.
+
+### Structure
+
+#### Model
+
+You can construct your own Neural Network architecture in PyTorch by simply defining your `nn.Module` class inside your custom [`IFreqaiModel` file](#using-different-prediction-models) and then using that class in your `def train()` function. Here is an example of logistic regression model implementation using PyTorch (should be used with nn.BCELoss criterion) for classification tasks.
+
+```python
+
+class LogisticRegression(nn.Module):
+ def __init__(self, input_size: int):
+ super().__init__()
+ # Define your layers
+ self.linear = nn.Linear(input_size, 1)
+ self.activation = nn.Sigmoid()
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ # Define the forward pass
+ out = self.linear(x)
+ out = self.activation(out)
+ return out
+
+class MyCoolPyTorchClassifier(BasePyTorchClassifier):
+ """
+ This is a custom IFreqaiModel showing how a user might setup their own
+ custom Neural Network architecture for their training.
+ """
+
+ @property
+ def data_convertor(self) -> PyTorchDataConvertor:
+ return DefaultPyTorchDataConvertor(target_tensor_type=torch.float)
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ config = self.freqai_info.get("model_training_parameters", {})
+ self.learning_rate: float = config.get("learning_rate", 3e-4)
+ self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+
+ def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ """
+ User sets up the training and test data to fit their desired model here
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
+ """
+
+ class_names = self.get_class_names()
+ self.convert_label_column_to_int(data_dictionary, dk, class_names)
+ n_features = data_dictionary["train_features"].shape[-1]
+ model = LogisticRegression(
+ input_dim=n_features
+ )
+ model.to(self.device)
+ optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
+ criterion = torch.nn.CrossEntropyLoss()
+ init_model = self.get_init_model(dk.pair)
+ trainer = PyTorchModelTrainer(
+ model=model,
+ optimizer=optimizer,
+ criterion=criterion,
+ model_meta_data={"class_names": class_names},
+ device=self.device,
+ init_model=init_model,
+ data_convertor=self.data_convertor,
+ **self.trainer_kwargs,
+ )
+ trainer.fit(data_dictionary, self.splits)
+ return trainer
+
+```
+
+#### Trainer
+
+The `PyTorchModelTrainer` performs the idiomatic PyTorch train loop:
+Define our model, loss function, and optimizer, and then move them to the appropriate device (GPU or CPU). Inside the loop, we iterate through the batches in the dataloader, move the data to the device, compute the prediction and loss, backpropagate, and update the model parameters using the optimizer.
+
+In addition, the trainer is responsible for the following:
+ - saving and loading the model
+ - converting the data from `pandas.DataFrame` to `torch.Tensor`.
+
+#### Integration with Freqai module
+
+Like all freqai models, PyTorch models inherit `IFreqaiModel`. `IFreqaiModel` declares three abstract methods: `train`, `fit`, and `predict`. we implement these methods in three levels of hierarchy.
+From top to bottom:
+
+1. `BasePyTorchModel` - Implements the `train` method. all `BasePyTorch*` inherit it. responsible for general data preparation (e.g., data normalization) and calling the `fit` method. Sets `device` attribute used by children classes. Sets `model_type` attribute used by the parent class.
+2. `BasePyTorch*` - Implements the `predict` method. Here, the `*` represents a group of algorithms, such as classifiers or regressors. responsible for data preprocessing, predicting, and postprocessing if needed.
+3. `PyTorch*Classifier` / `PyTorch*Regressor` - implements the `fit` method. responsible for the main train flaw, where we initialize the trainer and model objects.
+
+
+
+#### Full example
+
+Building a PyTorch regressor using MLP (multilayer perceptron) model, MSELoss criterion, and AdamW optimizer.
+
+```python
+class PyTorchMLPRegressor(BasePyTorchRegressor):
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ config = self.freqai_info.get("model_training_parameters", {})
+ self.learning_rate: float = config.get("learning_rate", 3e-4)
+ self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+
+ def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ n_features = data_dictionary["train_features"].shape[-1]
+ model = PyTorchMLPModel(
+ input_dim=n_features,
+ output_dim=1,
+ **self.model_kwargs
+ )
+ model.to(self.device)
+ optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
+ criterion = torch.nn.MSELoss()
+ init_model = self.get_init_model(dk.pair)
+ trainer = PyTorchModelTrainer(
+ model=model,
+ optimizer=optimizer,
+ criterion=criterion,
+ device=self.device,
+ init_model=init_model,
+ target_tensor_type=torch.float,
+ **self.trainer_kwargs,
+ )
+ trainer.fit(data_dictionary)
+ return trainer
+```
+
+Here we create a `PyTorchMLPRegressor` class that implements the `fit` method. The `fit` method specifies the training building blocks: model, optimizer, criterion, and trainer. We inherit both `BasePyTorchRegressor` and `BasePyTorchModel`, where the former implements the `predict` method that is suitable for our regression task, and the latter implements the train method.
+
+??? Note "Setting Class Names for Classifiers"
+ When using classifiers, the user must declare the class names (or targets) by overriding the `IFreqaiModel.class_names` attribute. This is achieved by setting `self.freqai.class_names` in the FreqAI strategy inside the `set_freqai_targets` method.
+
+ For example, if you are using a binary classifier to predict price movements as up or down, you can set the class names as follows:
+ ```python
+ def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ self.freqai.class_names = ["down", "up"]
+ dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
+ dataframe["close"], 'up', 'down')
+
+ return dataframe
+ ```
+ To see a full example, you can refer to the [classifier test strategy class](https://github.com/freqtrade/freqtrade/blob/develop/tests/strategy/strats/freqai_test_classifier.py).
diff --git a/docs/freqai-feature-engineering.md b/docs/freqai-feature-engineering.md
index 6389bd9e5..05c6db523 100644
--- a/docs/freqai-feature-engineering.md
+++ b/docs/freqai-feature-engineering.md
@@ -6,8 +6,8 @@ Low level feature engineering is performed in the user strategy within a set of
| Function | Description |
|---------------|-------------|
-| `feature_engineering__expand_all()` | This optional function will automatically expand the defined features on the config defined `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
-| `feature_engineering__expand_basic()` | This optional function will automatically expand the defined features on the config defined `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. Note: this function does *not* expand across `include_periods_candles`.
+| `feature_engineering_expand_all()` | This optional function will automatically expand the defined features on the config defined `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
+| `feature_engineering_expand_basic()` | This optional function will automatically expand the defined features on the config defined `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. Note: this function does *not* expand across `include_periods_candles`.
| `feature_engineering_standard()` | This optional function will be called once with the dataframe of the base timeframe. This is the final function to be called, which means that the dataframe entering this function will contain all the features and columns from the base asset created by the other `feature_engineering_expand` functions. This function is a good place to do custom exotic feature extractions (e.g. tsfresh). This function is also a good place for any feature that should not be auto-expanded upon (e.g., day of the week).
| `set_freqai_targets()` | Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals.
@@ -182,11 +182,11 @@ In total, the number of features the user of the presented example strat has cre
$= 3 * 3 * 3 * 2 * 2 = 108$.
- ### Gain finer control over `feature_engineering_*` functions with `metadata`
+### Gain finer control over `feature_engineering_*` functions with `metadata`
All `feature_engineering_*` and `set_freqai_targets()` functions are passed a `metadata` dictionary which contains information about the `pair`, `tf` (timeframe), and `period` that FreqAI is automating for feature building. As such, a user can use `metadata` inside `feature_engineering_*` functions as criteria for blocking/reserving features for certain timeframes, periods, pairs etc.
- ```py
+ ```python
def feature_engineering_expand_all(self, dataframe, period, metadata, **kwargs):
if metadata["tf"] == "1h":
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md
index 9822a895a..9ed3d6dce 100644
--- a/docs/freqai-parameter-table.md
+++ b/docs/freqai-parameter-table.md
@@ -86,6 +86,27 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `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`
+### PyTorch parameters
+
+#### general
+
+| Parameter | Description |
+|------------|-------------|
+| | **Model training parameters within the `freqai.model_training_parameters` sub dictionary**
+| `learning_rate` | Learning rate to be passed to the optimizer.
**Datatype:** float.
Default: `3e-4`.
+| `model_kwargs` | Parameters to be passed to the model class.
**Datatype:** dict.
Default: `{}`.
+| `trainer_kwargs` | Parameters to be passed to the trainer class.
**Datatype:** dict.
Default: `{}`.
+
+#### trainer_kwargs
+
+| Parameter | Description |
+|------------|-------------|
+| | **Model training parameters within the `freqai.model_training_parameters.model_kwargs` sub dictionary**
+| `max_iters` | The number of training iterations to run. iteration here refers to the number of times we call self.optimizer.step(). used to calculate n_epochs.
**Datatype:** int.
Default: `100`.
+| `batch_size` | The size of the batches to use during training..
**Datatype:** int.
Default: `64`.
+| `max_n_eval_batches` | The maximum number batches to use for evaluation..
**Datatype:** int, optional.
Default: `None`.
+
+
### Additional parameters
| Parameter | Description |
diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md
index f5679a4ba..f298dbf4d 100644
--- a/docs/freqai-reinforcement-learning.md
+++ b/docs/freqai-reinforcement-learning.md
@@ -180,7 +180,7 @@ As you begin to modify the strategy and the prediction model, you will quickly r
# 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_{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
diff --git a/docs/installation.md b/docs/installation.md
index 6e8488b9f..11de20e83 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -52,7 +52,7 @@ These requirements apply to both [Script Installation](#script-installation) and
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
-* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) (install instructions [below](#install-ta-lib))
+* [TA-Lib](https://ta-lib.github.io/ta-lib-python/) (install instructions [below](#install-ta-lib))
### Install code
@@ -210,7 +210,7 @@ sudo ./build_helpers/install_ta-lib.sh
##### TA-Lib manual installation
-Official webpage: https://mrjbq7.github.io/ta-lib/install.html
+[Official installation guide](https://ta-lib.github.io/ta-lib-python/install.html)
```bash
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
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 7f4215aef..550c3b54c 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.1.4
+mkdocs-material==9.1.6
mdx_truly_sane_lists==1.3
-pymdown-extensions==9.10
+pymdown-extensions==9.11
jinja2==3.1.2
diff --git a/docs/rest-api.md b/docs/rest-api.md
index 5f604ef43..860a44499 100644
--- a/docs/rest-api.md
+++ b/docs/rest-api.md
@@ -9,9 +9,6 @@ This same command can also be used to update freqUI, should there be a new relea
Once the bot is started in trade / dry-run mode (with `freqtrade trade`) - the UI will be available under the configured port below (usually `http://127.0.0.1:8080`).
-!!! info "Alpha release"
- FreqUI is still considered an alpha release - if you encounter bugs or inconsistencies please open a [FreqUI issue](https://github.com/freqtrade/frequi/issues/new/choose).
-
!!! Note "developers"
Developers should not use this method, but instead use the method described in the [freqUI repository](https://github.com/freqtrade/frequi) to get the source-code of freqUI.
diff --git a/docs/stoploss.md b/docs/stoploss.md
index 7af717955..d85902be0 100644
--- a/docs/stoploss.md
+++ b/docs/stoploss.md
@@ -23,10 +23,22 @@ These modes can be configured with these values:
'stoploss_on_exchange_limit_ratio': 0.99
```
-!!! Note
- Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), Gate (stop-limit), and Kucoin (stop-limit and stop-market) as of now.
- Do not set too low/tight stoploss value if using stop loss on exchange!
- If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work.
+Stoploss on exchange is only supported for the following exchanges, and not all exchanges support both stop-limit and stop-market.
+The Order-type will be ignored if only one mode is available.
+
+| Exchange | stop-loss type |
+|----------|-------------|
+| Binance | limit |
+| Binance Futures | market, limit |
+| Huobi | limit |
+| kraken | market, limit |
+| Gate | limit |
+| Okx | limit |
+| Kucoin | stop-limit, stop-market|
+
+!!! Note "Tight stoploss"
+ Do not set too low/tight stoploss value when using stop loss on exchange!
+ If set to low/tight you will have greater risk of missing fill on the order and stoploss will not work.
### stoploss_on_exchange and stoploss_on_exchange_limit_ratio
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index dc0ab0976..fe990790a 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -279,6 +279,7 @@ Return a summary of your profit/loss and performance.
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
+> **Bot started:** `2022-07-11 18:40:44`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
@@ -292,6 +293,7 @@ The relative profit of `15.2 Σ%` is be based on the starting capital - so in th
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
+Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
### /forceexit
diff --git a/docs/windows_installation.md b/docs/windows_installation.md
index 43d6728ee..0327f21e5 100644
--- a/docs/windows_installation.md
+++ b/docs/windows_installation.md
@@ -24,9 +24,9 @@ git clone https://github.com/freqtrade/freqtrade.git
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
-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).
+As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.8, 3.9, 3.10 and 3.11) and for 64bit Windows.
+These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
-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
@@ -45,8 +45,6 @@ freqtrade
The above installation script assumes you're using powershell on a 64bit windows.
Commands for the legacy CMD windows console may differ.
-> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222)
-
### Error during installation on Windows
``` bash
diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
index 6ba045adf..f8955b295 100644
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -1,5 +1,5 @@
""" Freqtrade bot """
-__version__ = '2023.3.dev'
+__version__ = '2023.4.dev'
if 'dev' in __version__:
from pathlib import Path
diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py
index adc5e65df..0c2f0d1b8 100644
--- a/freqtrade/configuration/timerange.py
+++ b/freqtrade/configuration/timerange.py
@@ -116,7 +116,7 @@ class TimeRange:
:param text: value from --timerange
:return: Start and End range period
"""
- if text is None:
+ if not text:
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index ebb946221..62b0f83be 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -64,6 +64,7 @@ USERPATH_FREQAIMODELS = 'freqaimodels'
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
WEBHOOK_FORMAT_OPTIONS = ['form', 'json', 'raw']
FULL_DATAFRAME_THRESHOLD = 100
+CUSTOM_TAG_MAX_LENGTH = 255
ENV_VAR_PREFIX = 'FREQTRADE__'
@@ -598,7 +599,7 @@ CONF_SCHEMA = {
"model_type": {"type": "string", "default": "PPO"},
"policy_type": {"type": "string", "default": "MlpPolicy"},
"net_arch": {"type": "array", "default": [128, 128]},
- "randomize_startinng_position": {"type": "boolean", "default": False},
+ "randomize_starting_position": {"type": "boolean", "default": False},
"model_reward_parameters": {
"type": "object",
"properties": {
diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py
index 3567f4112..c5905acde 100644
--- a/freqtrade/data/btanalysis.py
+++ b/freqtrade/data/btanalysis.py
@@ -246,14 +246,8 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
"""
Compatibility support for older backtest data.
"""
- df['open_date'] = pd.to_datetime(df['open_date'],
- utc=True,
- infer_datetime_format=True
- )
- df['close_date'] = pd.to_datetime(df['close_date'],
- utc=True,
- infer_datetime_format=True
- )
+ df['open_date'] = pd.to_datetime(df['open_date'], utc=True)
+ df['close_date'] = pd.to_datetime(df['close_date'], utc=True)
# Compatibility support for pre short Columns
if 'is_short' not in df.columns:
df['is_short'] = False
diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py
index 7ce98de42..2d3855d87 100644
--- a/freqtrade/data/converter.py
+++ b/freqtrade/data/converter.py
@@ -34,7 +34,7 @@ def ohlcv_to_dataframe(ohlcv: list, timeframe: str, pair: str, *,
cols = DEFAULT_DATAFRAME_COLUMNS
df = DataFrame(ohlcv, columns=cols)
- df['date'] = to_datetime(df['date'], unit='ms', utc=True, infer_datetime_format=True)
+ df['date'] = to_datetime(df['date'], unit='ms', utc=True)
# Some exchanges return int values for Volume and even for OHLC.
# Convert them since TA-LIB indicators used in the strategy assume floats
diff --git a/freqtrade/data/history/featherdatahandler.py b/freqtrade/data/history/featherdatahandler.py
index bb387fc84..28a12fb29 100644
--- a/freqtrade/data/history/featherdatahandler.py
+++ b/freqtrade/data/history/featherdatahandler.py
@@ -63,10 +63,7 @@ class FeatherDataHandler(IDataHandler):
pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
- pairdata['date'] = to_datetime(pairdata['date'],
- unit='ms',
- utc=True,
- infer_datetime_format=True)
+ pairdata['date'] = to_datetime(pairdata['date'], unit='ms', utc=True)
return pairdata
def ohlcv_append(
diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py
index f016c0ec1..ed7a33f8e 100644
--- a/freqtrade/data/history/jsondatahandler.py
+++ b/freqtrade/data/history/jsondatahandler.py
@@ -75,10 +75,7 @@ class JsonDataHandler(IDataHandler):
return DataFrame(columns=self._columns)
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
- pairdata['date'] = to_datetime(pairdata['date'],
- unit='ms',
- utc=True,
- infer_datetime_format=True)
+ pairdata['date'] = to_datetime(pairdata['date'], unit='ms', utc=True)
return pairdata
def ohlcv_append(
diff --git a/freqtrade/data/history/parquetdatahandler.py b/freqtrade/data/history/parquetdatahandler.py
index 57581861d..e6b2481d2 100644
--- a/freqtrade/data/history/parquetdatahandler.py
+++ b/freqtrade/data/history/parquetdatahandler.py
@@ -62,10 +62,7 @@ class ParquetDataHandler(IDataHandler):
pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
- pairdata['date'] = to_datetime(pairdata['date'],
- unit='ms',
- utc=True,
- infer_datetime_format=True)
+ pairdata['date'] = to_datetime(pairdata['date'], unit='ms', utc=True)
return pairdata
def ohlcv_append(
diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py
index b815fb3ee..df10e40e5 100644
--- a/freqtrade/exchange/__init__.py
+++ b/freqtrade/exchange/__init__.py
@@ -8,15 +8,15 @@ from freqtrade.exchange.bitpanda import Bitpanda
from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.bybit import Bybit
from freqtrade.exchange.coinbasepro import Coinbasepro
-from freqtrade.exchange.exchange_utils import (amount_to_contract_precision, amount_to_contracts,
- amount_to_precision, available_exchanges,
- ccxt_exchanges, contracts_to_amount,
- date_minus_candles, is_exchange_known_ccxt,
- market_is_active, price_to_precision,
- timeframe_to_minutes, timeframe_to_msecs,
- timeframe_to_next_date, timeframe_to_prev_date,
- timeframe_to_seconds, validate_exchange,
- validate_exchanges)
+from freqtrade.exchange.exchange_utils import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
+ amount_to_contracts, amount_to_precision,
+ available_exchanges, ccxt_exchanges,
+ contracts_to_amount, date_minus_candles,
+ is_exchange_known_ccxt, market_is_active,
+ price_to_precision, timeframe_to_minutes,
+ timeframe_to_msecs, timeframe_to_next_date,
+ timeframe_to_prev_date, timeframe_to_seconds,
+ validate_exchange, validate_exchanges)
from freqtrade.exchange.gate import Gate
from freqtrade.exchange.hitbtc import Hitbtc
from freqtrade.exchange.huobi import Huobi
diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json
index 07fdcb5a4..0b9be0f55 100644
--- a/freqtrade/exchange/binance_leverage_tiers.json
+++ b/freqtrade/exchange/binance_leverage_tiers.json
@@ -999,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"
}
},
{
@@ -1023,7 +1023,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -1039,7 +1039,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -1055,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,
@@ -1133,9 +1133,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -1274,13 +1274,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
- "maxNotional": 200000.0,
+ "maxNotional": 600000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
- "notionalCap": "200000",
+ "notionalCap": "600000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "650.0"
@@ -1289,65 +1289,65 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
+ "minNotional": 600000.0,
+ "maxNotional": 1600000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
+ "notionalCap": "1600000",
+ "notionalFloor": "600000",
"maintMarginRatio": "0.1",
- "cum": "10650.0"
+ "cum": "30650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
+ "minNotional": 1600000.0,
+ "maxNotional": 2000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "5",
"initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
+ "notionalCap": "2000000",
+ "notionalFloor": "1600000",
"maintMarginRatio": "0.125",
- "cum": "23150.0"
+ "cum": "70650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 2000000.0,
+ "maxNotional": 6000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "6",
"initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
+ "notionalCap": "6000000",
+ "notionalFloor": "2000000",
"maintMarginRatio": "0.25",
- "cum": "148150.0"
+ "cum": "320650.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 5000000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "7",
"initialLeverage": "1",
- "notionalCap": "5000000",
- "notionalFloor": "3000000",
+ "notionalCap": "10000000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.5",
- "cum": "898150.0"
+ "cum": "1820650.0"
}
}
],
@@ -1743,6 +1743,120 @@
}
}
],
+ "AMB/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"
+ }
+ }
+ ],
"ANC/BUSD:BUSD": [
{
"tier": 1.0,
@@ -2304,10 +2418,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",
@@ -2320,10 +2434,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",
@@ -2334,13 +2448,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"
@@ -2349,49 +2463,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"
}
}
],
@@ -2546,13 +2676,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
- "maxNotional": 300000.0,
+ "maxNotional": 900000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "20",
- "notionalCap": "300000",
+ "notionalCap": "900000",
"notionalFloor": "25000",
"maintMarginRatio": "0.02",
"cum": "150.0"
@@ -2561,39 +2691,39 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 300000.0,
- "maxNotional": 1200000.0,
+ "minNotional": 900000.0,
+ "maxNotional": 1800000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "10",
- "notionalCap": "1200000",
- "notionalFloor": "300000",
+ "notionalCap": "1800000",
+ "notionalFloor": "900000",
"maintMarginRatio": "0.05",
- "cum": "9150.0"
+ "cum": "27150.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1200000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 1800000.0,
+ "maxNotional": 4800000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "5",
- "notionalCap": "3000000",
- "notionalFloor": "1200000",
+ "notionalCap": "4800000",
+ "notionalFloor": "1800000",
"maintMarginRatio": "0.1",
- "cum": "69150.0"
+ "cum": "117150.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 3000000.0,
+ "minNotional": 4800000.0,
"maxNotional": 6000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
@@ -2601,9 +2731,9 @@
"bracket": "6",
"initialLeverage": "4",
"notionalCap": "6000000",
- "notionalFloor": "3000000",
+ "notionalFloor": "4800000",
"maintMarginRatio": "0.125",
- "cum": "144150.0"
+ "cum": "237150.0"
}
},
{
@@ -2619,7 +2749,7 @@
"notionalCap": "18000000",
"notionalFloor": "6000000",
"maintMarginRatio": "0.25",
- "cum": "894150.0"
+ "cum": "987150.0"
}
},
{
@@ -2635,7 +2765,7 @@
"notionalCap": "30000000",
"notionalFloor": "18000000",
"maintMarginRatio": "0.5",
- "cum": "5394150.0"
+ "cum": "5487150.0"
}
}
],
@@ -2737,6 +2867,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,
@@ -2760,10 +3020,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",
@@ -2774,13 +3034,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"
@@ -2789,49 +3049,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"
}
}
],
@@ -3364,10 +3640,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -3380,10 +3656,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 15.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "15",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -3396,10 +3672,10 @@
"minNotional": 25000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maxLeverage": 6.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
+ "initialLeverage": "6",
"notionalCap": "100000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
@@ -3458,13 +3734,13 @@
"tier": 7.0,
"currency": "BUSD",
"minNotional": 3000000.0,
- "maxNotional": 8000000.0,
+ "maxNotional": 4000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "7",
"initialLeverage": "1",
- "notionalCap": "8000000",
+ "notionalCap": "4000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
"cum": "949400.0"
@@ -3639,14 +3915,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"
}
},
@@ -3655,14 +3931,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"
}
},
@@ -3670,70 +3946,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,
@@ -3741,9 +4017,9 @@
"bracket": "7",
"initialLeverage": "4",
"notionalCap": "5000000",
- "notionalFloor": "2000000",
+ "notionalFloor": "3200000",
"maintMarginRatio": "0.125",
- "cum": "108067.5"
+ "cum": "162080.0"
}
},
{
@@ -3751,15 +4027,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"
}
},
{
@@ -3775,7 +4051,7 @@
"notionalCap": "15000000",
"notionalFloor": "10000000",
"maintMarginRatio": "0.25",
- "cum": "1150567.5"
+ "cum": "1287080.0"
}
},
{
@@ -3791,7 +4067,7 @@
"notionalCap": "20000000",
"notionalFloor": "15000000",
"maintMarginRatio": "0.5",
- "cum": "4900567.5"
+ "cum": "5037080.0"
}
}
],
@@ -4763,15 +5039,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"
}
},
{
@@ -4787,7 +5063,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -4803,7 +5079,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -4819,77 +5095,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,
@@ -4897,9 +5173,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -5325,7 +5601,7 @@
}
}
],
- "BTC/USDT:USDT-230331": [
+ "BTC/USDT:USDT-230630": [
{
"tier": 1.0,
"currency": "USDT",
@@ -5740,10 +6016,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",
@@ -5756,10 +6032,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",
@@ -5770,13 +6046,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"
@@ -5785,33 +6061,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"
}
},
{
@@ -5819,15 +6095,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"
}
}
],
@@ -5838,10 +6130,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",
@@ -5854,10 +6146,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",
@@ -5868,13 +6160,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"
@@ -5883,33 +6175,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"
}
},
{
@@ -5917,15 +6209,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"
}
}
],
@@ -5936,10 +6244,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",
@@ -5952,10 +6260,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",
@@ -5966,13 +6274,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"
@@ -5981,49 +6289,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"
}
}
],
@@ -6980,10 +7304,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",
@@ -6996,10 +7320,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",
@@ -7010,13 +7334,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"
@@ -7025,33 +7349,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"
}
},
{
@@ -7059,15 +7383,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"
}
}
],
@@ -7160,10 +7500,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",
@@ -7176,10 +7516,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",
@@ -7192,10 +7532,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",
@@ -7222,13 +7562,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"
@@ -7237,17 +7577,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"
}
}
],
@@ -7340,10 +7680,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",
@@ -7356,10 +7696,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",
@@ -7370,13 +7710,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"
@@ -7385,49 +7725,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"
}
}
],
@@ -7634,10 +7990,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",
@@ -7650,10 +8006,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",
@@ -7664,13 +8020,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"
@@ -7679,49 +8035,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"
}
}
],
@@ -8025,14 +8397,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"
}
},
@@ -8041,14 +8413,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"
}
},
@@ -8056,112 +8428,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"
}
}
],
@@ -8706,13 +9078,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"
@@ -8721,49 +9093,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"
}
}
],
@@ -9017,15 +9405,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"
}
},
{
@@ -9041,7 +9429,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -9057,7 +9445,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -9073,77 +9461,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,
@@ -9151,9 +9539,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -9277,15 +9665,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"
}
},
{
@@ -9301,7 +9689,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -9317,7 +9705,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -9333,77 +9721,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,
@@ -9411,9 +9799,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -9741,7 +10129,7 @@
}
}
],
- "ETH/USDT:USDT-230331": [
+ "ETH/USDT:USDT-230630": [
{
"tier": 1.0,
"currency": "USDT",
@@ -11036,10 +11424,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",
@@ -11052,10 +11440,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",
@@ -11068,10 +11456,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",
@@ -11098,13 +11486,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"
@@ -11113,17 +11501,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"
}
}
],
@@ -11329,14 +11717,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"
}
},
@@ -11353,103 +11741,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"
}
}
],
@@ -11460,10 +11848,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -11476,10 +11864,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -11492,10 +11880,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",
@@ -11538,13 +11926,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -11672,10 +12060,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",
@@ -11688,10 +12076,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",
@@ -11702,13 +12090,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
- "maxNotional": 100000.0,
+ "maxNotional": 480000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
- "notionalCap": "100000",
+ "notionalCap": "480000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "650.0"
@@ -11717,49 +12105,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": 5000000.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": "5000000",
- "notionalFloor": "1000000",
+ "notionalCap": "8000000",
+ "notionalFloor": "4800000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "1456650.0"
}
}
],
@@ -12073,6 +12477,120 @@
}
}
],
+ "HFT/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"
+ }
+ }
+ ],
"HIGH/USDT:USDT": [
{
"tier": 1.0,
@@ -12194,10 +12712,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",
@@ -12210,10 +12728,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",
@@ -12224,13 +12742,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"
@@ -12239,49 +12757,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"
}
}
],
@@ -12481,104 +13015,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,
@@ -12586,10 +13022,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",
@@ -12602,10 +13038,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",
@@ -12616,13 +13052,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"
@@ -12631,49 +13067,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"
}
}
],
@@ -12775,105 +13227,7 @@
}
}
],
- "IMX/USDT:USDT": [
- {
- "tier": 1.0,
- "currency": "USDT",
- "minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.01,
- "maxLeverage": 20.0,
- "info": {
- "bracket": "1",
- "initialLeverage": "20",
- "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": 10.0,
- "info": {
- "bracket": "2",
- "initialLeverage": "10",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
- }
- },
- {
- "tier": 3.0,
- "currency": "USDT",
- "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": "700.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": "5700.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": "11950.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": "386950.0"
- }
- }
- ],
- "INJ/USDT:USDT": [
+ "ID/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@@ -12896,10 +13250,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",
@@ -12910,13 +13264,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"
@@ -12925,33 +13279,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"
}
},
{
@@ -12959,15 +13313,259 @@
"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"
+ }
+ }
+ ],
+ "IMX/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"
+ }
+ }
+ ],
+ "INJ/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "25",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.02",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.025",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "10",
+ "notionalCap": "600000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "650.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 1600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "5",
+ "notionalCap": "1600000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.1",
+ "cum": "30650.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 1600000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "4",
+ "notionalCap": "2000000",
+ "notionalFloor": "1600000",
+ "maintMarginRatio": "0.125",
+ "cum": "70650.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.25",
+ "cum": "320650.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 6000000.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "1",
+ "notionalCap": "10000000",
+ "notionalFloor": "6000000",
+ "maintMarginRatio": "0.5",
+ "cum": "1820650.0"
}
}
],
@@ -13379,6 +13977,120 @@
}
}
],
+ "JOE/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"
+ }
+ }
+ ],
"KAVA/USDT:USDT": [
{
"tier": 1.0,
@@ -14116,20 +14828,20 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 2000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "2000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386885.0"
}
}
],
- "LINA/USDT:USDT": [
+ "LEVER/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@@ -14152,10 +14864,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",
@@ -14166,13 +14878,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"
@@ -14181,33 +14893,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"
}
},
{
@@ -14215,15 +14927,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": "3000000",
- "notionalFloor": "1000000",
+ "notionalCap": "5000000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "898150.0"
+ }
+ }
+ ],
+ "LINA/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": 900000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "10",
+ "notionalCap": "900000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "650.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 900000.0,
+ "maxNotional": 2400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "5",
+ "notionalCap": "2400000",
+ "notionalFloor": "900000",
+ "maintMarginRatio": "0.1",
+ "cum": "45650.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 2400000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "4",
+ "notionalCap": "3000000",
+ "notionalFloor": "2400000",
+ "maintMarginRatio": "0.125",
+ "cum": "105650.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "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": "7",
+ "initialLeverage": "1",
+ "notionalCap": "15000000",
+ "notionalFloor": "9000000",
+ "maintMarginRatio": "0.5",
+ "cum": "2730650.0"
}
}
],
@@ -14363,15 +15205,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"
}
},
{
@@ -14387,7 +15229,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -14403,7 +15245,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -14419,77 +15261,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,
@@ -14497,9 +15339,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -15031,15 +15873,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"
}
},
{
@@ -15055,7 +15897,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -15071,7 +15913,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -15087,77 +15929,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,
@@ -15165,107 +16007,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"
}
}
],
@@ -15617,14 +16361,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"
}
},
@@ -15633,95 +16377,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": 600000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maxNotional": 900000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "600000",
+ "initialLeverage": "20",
+ "notionalCap": "900000",
"notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 1600000.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": "1600000",
- "notionalFloor": "600000",
- "maintMarginRatio": "0.1",
- "cum": "30650.0"
+ "initialLeverage": "10",
+ "notionalCap": "1800000",
+ "notionalFloor": "900000",
+ "maintMarginRatio": "0.05",
+ "cum": "22675.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 1800000.0,
+ "maxNotional": 4800000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "3000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "70650.0"
+ "initialLeverage": "5",
+ "notionalCap": "4800000",
+ "notionalFloor": "1800000",
+ "maintMarginRatio": "0.1",
+ "cum": "112675.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 3000000.0,
+ "minNotional": 4800000.0,
"maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
+ "initialLeverage": "4",
"notionalCap": "6000000",
- "notionalFloor": "3000000",
- "maintMarginRatio": "0.25",
- "cum": "445650.0"
+ "notionalFloor": "4800000",
+ "maintMarginRatio": "0.125",
+ "cum": "232675.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
"minNotional": 6000000.0,
- "maxNotional": 10000000.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": "7",
+ "bracket": "8",
"initialLeverage": "1",
- "notionalCap": "10000000",
- "notionalFloor": "6000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "18000000",
"maintMarginRatio": "0.5",
- "cum": "1945650.0"
+ "cum": "5482675.0"
}
}
],
@@ -15876,13 +16636,13 @@
"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": "80.0"
@@ -15891,97 +16651,97 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 400000.0,
+ "minNotional": 600000.0,
+ "maxNotional": 900000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
- "notionalCap": "400000",
- "notionalFloor": "50000",
+ "notionalCap": "900000",
+ "notionalFloor": "600000",
"maintMarginRatio": "0.025",
- "cum": "830.0"
+ "cum": "9080.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 400000.0,
- "maxNotional": 800000.0,
+ "minNotional": 900000.0,
+ "maxNotional": 1800000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
- "notionalCap": "800000",
- "notionalFloor": "400000",
+ "notionalCap": "1800000",
+ "notionalFloor": "900000",
"maintMarginRatio": "0.05",
- "cum": "10830.0"
+ "cum": "31580.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 800000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 1800000.0,
+ "maxNotional": 4800000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "800000",
+ "notionalCap": "4800000",
+ "notionalFloor": "1800000",
"maintMarginRatio": "0.1",
- "cum": "50830.0"
+ "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": "100830.0"
+ "cum": "241580.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 12000000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 18000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
- "notionalCap": "12000000",
- "notionalFloor": "5000000",
+ "notionalCap": "18000000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.25",
- "cum": "725830.0"
+ "cum": "991580.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 12000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 18000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "20000000",
- "notionalFloor": "12000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "18000000",
"maintMarginRatio": "0.5",
- "cum": "3725830.0"
+ "cum": "5491580.0"
}
}
],
@@ -16105,14 +16865,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"
}
},
@@ -16121,79 +16881,111 @@
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 25.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
+ "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": 10.0,
+ "maxNotional": 450000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "100000",
+ "initialLeverage": "20",
+ "notionalCap": "450000",
"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": 450000.0,
+ "maxNotional": 900000.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": "900000",
+ "notionalFloor": "450000",
+ "maintMarginRatio": "0.05",
+ "cum": "11645.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "minNotional": 900000.0,
+ "maxNotional": 2400000.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": "2400000",
+ "notionalFloor": "900000",
+ "maintMarginRatio": "0.1",
+ "cum": "56645.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "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": "6",
+ "bracket": "8",
"initialLeverage": "1",
- "notionalCap": "5000000",
- "notionalFloor": "1000000",
+ "notionalCap": "15000000",
+ "notionalFloor": "9000000",
"maintMarginRatio": "0.5",
- "cum": "386950.0"
+ "cum": "2741645.0"
}
}
],
@@ -16204,10 +16996,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",
@@ -16220,10 +17012,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",
@@ -16234,13 +17026,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"
@@ -16249,49 +17041,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"
}
}
],
@@ -16302,10 +17110,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -16318,10 +17126,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -16334,10 +17142,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",
@@ -16380,13 +17188,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "3000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -16968,13 +17776,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"
@@ -16983,49 +17791,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"
}
}
],
@@ -17164,13 +17988,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"
@@ -17179,49 +18003,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"
}
}
],
@@ -17574,10 +18414,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 11.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "11",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -17590,10 +18430,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -17606,10 +18446,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",
@@ -17652,13 +18492,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -18073,6 +18913,120 @@
}
}
],
+ "RDNT/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"
+ }
+ }
+ ],
"REEF/USDT:USDT": [
{
"tier": 1.0,
@@ -18208,13 +19162,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 25000.0,
- "maxNotional": 200000.0,
+ "maxNotional": 600000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
- "notionalCap": "200000",
+ "notionalCap": "600000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "650.0"
@@ -18223,65 +19177,65 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
+ "minNotional": 600000.0,
+ "maxNotional": 1600000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
+ "notionalCap": "1600000",
+ "notionalFloor": "600000",
"maintMarginRatio": "0.1",
- "cum": "10650.0"
+ "cum": "30650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
+ "minNotional": 1600000.0,
+ "maxNotional": 2000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "5",
"initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
+ "notionalCap": "2000000",
+ "notionalFloor": "1600000",
"maintMarginRatio": "0.125",
- "cum": "23150.0"
+ "cum": "70650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 2000000.0,
+ "maxNotional": 6000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "6",
"initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
+ "notionalCap": "6000000",
+ "notionalFloor": "2000000",
"maintMarginRatio": "0.25",
- "cum": "148150.0"
+ "cum": "320650.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 5000000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "7",
"initialLeverage": "1",
- "notionalCap": "5000000",
- "notionalFloor": "3000000",
+ "notionalCap": "10000000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.5",
- "cum": "898150.0"
+ "cum": "1820650.0"
}
}
],
@@ -18290,80 +19244,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"
}
}
],
@@ -18896,10 +19882,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -18912,10 +19898,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -18928,10 +19914,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",
@@ -18974,13 +19960,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -19678,10 +20664,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",
@@ -19692,13 +20678,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"
@@ -19707,81 +20693,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"
}
}
],
@@ -20200,10 +21186,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",
@@ -20216,10 +21202,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",
@@ -20232,10 +21218,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",
@@ -20278,13 +21264,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"
@@ -20520,104 +21506,6 @@
}
],
"SUSHI/USDT:USDT": [
- {
- "tier": 1.0,
- "currency": "USDT",
- "minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.01,
- "maxLeverage": 20.0,
- "info": {
- "bracket": "1",
- "initialLeverage": "20",
- "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": 10.0,
- "info": {
- "bracket": "2",
- "initialLeverage": "10",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
- }
- },
- {
- "tier": 3.0,
- "currency": "USDT",
- "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": "700.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": "5700.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": "11950.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": "386950.0"
- }
- }
- ],
- "SXP/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@@ -20654,13 +21542,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"
@@ -20669,49 +21557,179 @@
{
"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"
+ }
+ }
+ ],
+ "SXP/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 15.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "15",
+ "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": 10.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "10",
+ "notionalCap": "25000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.025",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 8.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "8",
+ "notionalCap": "600000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "650.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 1600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "5",
+ "notionalCap": "1600000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.1",
+ "cum": "30650.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 1600000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "4",
+ "notionalCap": "2000000",
+ "notionalFloor": "1600000",
+ "maintMarginRatio": "0.125",
+ "cum": "70650.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.25",
+ "cum": "320650.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 6000000.0,
+ "maxNotional": 6500000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "1",
+ "notionalCap": "6500000",
+ "notionalFloor": "6000000",
+ "maintMarginRatio": "0.5",
+ "cum": "1820650.0"
}
}
],
@@ -20950,10 +21968,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -20966,10 +21984,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -20982,10 +22000,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",
@@ -21028,13 +22046,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -21042,104 +22060,6 @@
}
],
"TLM/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": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
- "info": {
- "bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "700.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": "5700.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": "11950.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": "386950.0"
- }
- }
- ],
- "TOMO/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@@ -21162,10 +22082,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",
@@ -21176,13 +22096,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"
@@ -21191,33 +22111,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"
}
},
{
@@ -21225,15 +22145,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": "3000000",
- "notionalFloor": "1000000",
+ "notionalCap": "5000000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "898150.0"
+ }
+ }
+ ],
+ "TOMO/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"
}
}
],
@@ -21814,10 +22864,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maxLeverage": 8.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "8",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
@@ -21830,10 +22880,10 @@
"minNotional": 5000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxLeverage": 7.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
+ "initialLeverage": "7",
"notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
@@ -21846,10 +22896,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",
@@ -21892,13 +22942,13 @@
"tier": 6.0,
"currency": "BUSD",
"minNotional": 1000000.0,
- "maxNotional": 5000000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
- "notionalCap": "5000000",
+ "notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
@@ -21942,13 +22992,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
- "maxNotional": 400000.0,
+ "maxNotional": 900000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "20",
- "notionalCap": "400000",
+ "notionalCap": "900000",
"notionalFloor": "50000",
"maintMarginRatio": "0.025",
"cum": "770.0"
@@ -21957,81 +23007,81 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 400000.0,
- "maxNotional": 800000.0,
+ "minNotional": 900000.0,
+ "maxNotional": 1800000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "10",
- "notionalCap": "800000",
- "notionalFloor": "400000",
+ "notionalCap": "1800000",
+ "notionalFloor": "900000",
"maintMarginRatio": "0.05",
- "cum": "10770.0"
+ "cum": "23270.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 800000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 1800000.0,
+ "maxNotional": 4800000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "800000",
+ "notionalCap": "4800000",
+ "notionalFloor": "1800000",
"maintMarginRatio": "0.1",
- "cum": "50770.0"
+ "cum": "113270.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": "100770.0"
+ "cum": "233270.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 12000000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 18000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
- "notionalCap": "12000000",
- "notionalFloor": "5000000",
+ "notionalCap": "18000000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.25",
- "cum": "725770.0"
+ "cum": "983270.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 12000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 18000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
- "notionalCap": "20000000",
- "notionalFloor": "12000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "18000000",
"maintMarginRatio": "0.5",
- "cum": "3725770.0"
+ "cum": "5483270.0"
}
}
],
@@ -22364,13 +23414,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"
@@ -22379,17 +23429,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"
}
}
],
@@ -23067,15 +24117,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"
}
},
{
@@ -23091,7 +24141,7 @@
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "42.5"
+ "cum": "45.0"
}
},
{
@@ -23107,7 +24157,7 @@
"notionalCap": "250000",
"notionalFloor": "50000",
"maintMarginRatio": "0.02",
- "cum": "542.5"
+ "cum": "545.0"
}
},
{
@@ -23123,77 +24173,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,
@@ -23201,9 +24251,9 @@
"bracket": "10",
"initialLeverage": "1",
"notionalCap": "50000000",
- "notionalFloor": "20000000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.5",
- "cum": "6233042.5"
+ "cum": "9933045.0"
}
}
],
@@ -23321,6 +24371,120 @@
}
}
],
+ "XVS/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"
+ }
+ }
+ ],
"YFI/USDT:USDT": [
{
"tier": 1.0,
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 92eb4b58a..ba7d79f97 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -30,13 +30,14 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFun
RetryableOrderError, TemporaryError)
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_credentials, retrier,
retrier_async)
-from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contract_precision,
- amount_to_contracts, amount_to_precision,
- contracts_to_amount, date_minus_candles,
- is_exchange_known_ccxt, market_is_active,
- price_to_precision, timeframe_to_minutes,
- timeframe_to_msecs, timeframe_to_next_date,
- timeframe_to_prev_date, timeframe_to_seconds)
+from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
+ amount_to_contract_precision, amount_to_contracts,
+ amount_to_precision, contracts_to_amount,
+ date_minus_candles, is_exchange_known_ccxt,
+ market_is_active, price_to_precision,
+ timeframe_to_minutes, timeframe_to_msecs,
+ timeframe_to_next_date, timeframe_to_prev_date,
+ timeframe_to_seconds)
from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
safe_value_fallback2)
@@ -59,6 +60,7 @@ class Exchange:
# or by specifying them in the configuration.
_ft_has_default: Dict = {
"stoploss_on_exchange": False,
+ "stop_price_param": "stopPrice",
"order_time_in_force": ["GTC"],
"ohlcv_params": {},
"ohlcv_candle_limit": 500,
@@ -80,6 +82,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 = {}
@@ -732,12 +736,14 @@ class Exchange:
"""
return amount_to_precision(amount, self.get_precision_amount(pair), self.precisionMode)
- def price_to_precision(self, pair: str, price: float) -> float:
+ def price_to_precision(self, pair: str, price: float, *, rounding_mode: int = ROUND) -> float:
"""
- Returns the price rounded up to the precision the Exchange accepts.
- Rounds up
+ Returns the price rounded to the precision the Exchange accepts.
+ The default price_rounding_mode in conf is ROUND.
+ For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
"""
- return price_to_precision(price, self.get_precision_price(pair), self.precisionMode)
+ return price_to_precision(price, self.get_precision_price(pair),
+ self.precisionMode, rounding_mode=rounding_mode)
def price_get_one_pip(self, pair: str, price: float) -> float:
"""
@@ -760,12 +766,12 @@ class Exchange:
return self._get_stake_amount_limit(pair, price, stoploss, 'min', leverage)
def get_max_pair_stake_amount(self, pair: str, price: float, leverage: float = 1.0) -> float:
- max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max')
+ max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max', leverage)
if max_stake_amount is None:
# * Should never be executed
raise OperationalException(f'{self.name}.get_max_pair_stake_amount should'
'never set max_stake_amount to None')
- return max_stake_amount / leverage
+ return max_stake_amount
def _get_stake_amount_limit(
self,
@@ -783,43 +789,41 @@ class Exchange:
except KeyError:
raise ValueError(f"Can't get market information for symbol {pair}")
+ if isMin:
+ # reserve some percent defined in config (5% default) + stoploss
+ margin_reserve: float = 1.0 + self._config.get('amount_reserve_percent',
+ DEFAULT_AMOUNT_RESERVE_PERCENT)
+ stoploss_reserve = (
+ margin_reserve / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
+ )
+ # it should not be more than 50%
+ stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
+ else:
+ margin_reserve = 1.0
+ stoploss_reserve = 1.0
+
stake_limits = []
limits = market['limits']
if (limits['cost'][limit] is not None):
stake_limits.append(
- self._contracts_to_amount(
- pair,
- limits['cost'][limit]
- )
+ self._contracts_to_amount(pair, limits['cost'][limit]) * stoploss_reserve
)
if (limits['amount'][limit] is not None):
stake_limits.append(
- self._contracts_to_amount(
- pair,
- limits['amount'][limit] * price
- )
+ self._contracts_to_amount(pair, limits['amount'][limit]) * price * margin_reserve
)
if not stake_limits:
return None if isMin else float('inf')
- # reserve some percent defined in config (5% default) + stoploss
- amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
- DEFAULT_AMOUNT_RESERVE_PERCENT)
- amount_reserve_percent = (
- amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
- )
- # it should not be more than 50%
- amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
-
# The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here.
# See also #2575 at github.
return self._get_stake_amount_considering_leverage(
- max(stake_limits) * amount_reserve_percent,
+ max(stake_limits) if isMin else min(stake_limits),
leverage or 1.0
- ) if isMin else min(stake_limits)
+ )
def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float) -> float:
"""
@@ -882,7 +886,7 @@ class Exchange:
'filled': _amount,
'remaining': 0.0,
'status': "closed",
- 'cost': (dry_order['amount'] * average) / leverage
+ 'cost': (dry_order['amount'] * average)
})
# market orders will always incurr taker fees
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
@@ -1040,6 +1044,13 @@ class Exchange:
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,
*,
@@ -1062,8 +1073,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:
@@ -1106,11 +1116,11 @@ class Exchange:
"""
if not self._ft_has.get('stoploss_on_exchange'):
raise OperationalException(f"stoploss is not implemented for {self.name}.")
-
+ price_param = self._ft_has['stop_price_param']
return (
- order.get('stopPrice', None) is None
- or ((side == "sell" and stop_loss > float(order['stopPrice'])) or
- (side == "buy" and stop_loss < float(order['stopPrice'])))
+ order.get(price_param, None) is None
+ or ((side == "sell" and stop_loss > float(order[price_param])) or
+ (side == "buy" and stop_loss < float(order[price_param])))
)
def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]:
@@ -1150,8 +1160,8 @@ class Exchange:
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({'stopPrice': stop_price})
+ # Verify if stopPrice works for your exchange, else configure stop_price_param
+ params.update({self._ft_has['stop_price_param']: stop_price})
return params
@retrier(retries=0)
@@ -1177,12 +1187,12 @@ class Exchange:
user_order_type = order_types.get('stoploss', 'market')
ordertype, user_order_type = self._get_stop_order_type(user_order_type)
-
- stop_price_norm = self.price_to_precision(pair, stop_price)
+ round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
+ stop_price_norm = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
limit_rate = None
if user_order_type == 'limit':
limit_rate = self._get_stop_limit_rate(stop_price, order_types, side)
- limit_rate = self.price_to_precision(pair, limit_rate)
+ limit_rate = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
if self._config['dry_run']:
dry_order = self.create_dry_run_order(
diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py
index 6d3371a59..83d2a214d 100644
--- a/freqtrade/exchange/exchange_utils.py
+++ b/freqtrade/exchange/exchange_utils.py
@@ -2,11 +2,12 @@
Exchange support utils
"""
from datetime import datetime, timedelta, timezone
-from math import ceil
+from math import ceil, floor
from typing import Any, Dict, List, Optional, Tuple
import ccxt
-from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, decimal_to_precision
+from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE,
+ TRUNCATE, decimal_to_precision)
from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED
from freqtrade.util import FtPrecise
@@ -219,35 +220,51 @@ def amount_to_contract_precision(
return amount
-def price_to_precision(price: float, price_precision: Optional[float],
- precisionMode: Optional[int]) -> float:
+def price_to_precision(
+ price: float,
+ price_precision: Optional[float],
+ precisionMode: Optional[int],
+ *,
+ rounding_mode: int = ROUND,
+) -> float:
"""
- Returns the price rounded up to the precision the Exchange accepts.
+ Returns the price rounded to the precision the Exchange accepts.
Partial Re-implementation of ccxt internal method decimal_to_precision(),
- which does not support rounding up
+ which does not support rounding up.
+ For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
+
TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and
align with amount_to_precision().
- !!! Rounds up
:param price: price to convert
:param price_precision: price precision to use. Used from markets[pair]['precision']['price']
:param precisionMode: precision mode to use. Should be used from precisionMode
one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE
+ :param rounding_mode: rounding mode to use. Defaults to ROUND
:return: price rounded up to the precision the Exchange accepts
-
"""
if price_precision is not None and precisionMode is not None:
- # price = float(decimal_to_precision(price, rounding_mode=ROUND,
- # precision=price_precision,
- # counting_mode=self.precisionMode,
- # ))
if precisionMode == TICK_SIZE:
+ if rounding_mode == ROUND:
+ ticks = price / price_precision
+ rounded_ticks = round(ticks)
+ return rounded_ticks * price_precision
precision = FtPrecise(price_precision)
price_str = FtPrecise(price)
missing = price_str % precision
if not missing == FtPrecise("0"):
- price = round(float(str(price_str - missing + precision)), 14)
- else:
- symbol_prec = price_precision
- big_price = price * pow(10, symbol_prec)
- price = ceil(big_price) / pow(10, symbol_prec)
+ return round(float(str(price_str - missing + precision)), 14)
+ return price
+ elif precisionMode in (SIGNIFICANT_DIGITS, DECIMAL_PLACES):
+ ndigits = round(price_precision)
+ if rounding_mode == ROUND:
+ return round(price, ndigits)
+ ticks = price * (10**ndigits)
+ if rounding_mode == ROUND_UP:
+ return ceil(ticks) / (10**ndigits)
+ if rounding_mode == TRUNCATE:
+ return int(ticks) / (10**ndigits)
+ if rounding_mode == ROUND_DOWN:
+ return floor(ticks) / (10**ndigits)
+ raise ValueError(f"Unknown rounding_mode {rounding_mode}")
+ raise ValueError(f"Unknown precisionMode {precisionMode}")
return price
diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py
index bf6d5b59c..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,12 @@ 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'],
@@ -50,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,
diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py
index b1a19fa69..c41bb6d56 100644
--- a/freqtrade/exchange/kraken.py
+++ b/freqtrade/exchange/kraken.py
@@ -12,6 +12,7 @@ from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, Invali
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
+from freqtrade.exchange.exchange_utils import ROUND_DOWN, ROUND_UP
from freqtrade.exchange.types import Tickers
@@ -109,6 +110,7 @@ class Kraken(Exchange):
if self.trading_mode == TradingMode.FUTURES:
params.update({'reduceOnly': True})
+ round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
if order_types.get('stoploss', 'market') == 'limit':
ordertype = "stop-loss-limit"
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
@@ -116,11 +118,11 @@ class Kraken(Exchange):
limit_rate = stop_price * limit_price_pct
else:
limit_rate = stop_price * (2 - limit_price_pct)
- params['price2'] = self.price_to_precision(pair, limit_rate)
+ params['price2'] = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
else:
ordertype = "stop-loss"
- stop_price = self.price_to_precision(pair, stop_price)
+ stop_price = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
if self._config['dry_run']:
dry_order = self.create_dry_run_order(
diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py
index a4fcaeca0..84b7deb7a 100644
--- a/freqtrade/exchange/okx.py
+++ b/freqtrade/exchange/okx.py
@@ -28,6 +28,7 @@ class Okx(Exchange):
"funding_fee_timeframe": "8h",
"stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True,
+ "stop_price_param": "stopLossPrice",
}
_ft_has_futures: Dict = {
"tickers_have_quoteVolume": False,
@@ -162,29 +163,12 @@ class Okx(Exchange):
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})
-
+ params = super()._get_stop_params(side, ordertype, 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)
diff --git a/freqtrade/freqai/RL/Base3ActionRLEnv.py b/freqtrade/freqai/RL/Base3ActionRLEnv.py
index de7953226..bb773658e 100644
--- a/freqtrade/freqai/RL/Base3ActionRLEnv.py
+++ b/freqtrade/freqai/RL/Base3ActionRLEnv.py
@@ -66,7 +66,7 @@ class Base3ActionRLEnv(BaseEnvironment):
elif action == Actions.Sell.value and not self.can_short:
self._update_total_profit()
self._position = Positions.Neutral
- trade_type = "neutral"
+ trade_type = "exit"
self._last_trade_tick = None
else:
print("case not defined")
@@ -74,7 +74,7 @@ class Base3ActionRLEnv(BaseEnvironment):
if trade_type is not None:
self.trade_history.append(
{'price': self.current_price(), 'index': self._current_tick,
- 'type': trade_type})
+ 'type': trade_type, 'profit': self.get_unrealized_profit()})
if (self._total_profit < self.max_drawdown or
self._total_unrealized_profit < self.max_drawdown):
diff --git a/freqtrade/freqai/RL/Base4ActionRLEnv.py b/freqtrade/freqai/RL/Base4ActionRLEnv.py
index 227122eaa..aebed71ab 100644
--- a/freqtrade/freqai/RL/Base4ActionRLEnv.py
+++ b/freqtrade/freqai/RL/Base4ActionRLEnv.py
@@ -52,16 +52,6 @@ class Base4ActionRLEnv(BaseEnvironment):
trade_type = None
if self.is_tradesignal(action):
- """
- Action: Neutral, position: Long -> Close Long
- Action: Neutral, position: Short -> Close Short
-
- Action: Long, position: Neutral -> Open Long
- Action: Long, position: Short -> Close Short and Open Long
-
- Action: Short, position: Neutral -> Open Short
- Action: Short, position: Long -> Close Long and Open Short
- """
if action == Actions.Neutral.value:
self._position = Positions.Neutral
@@ -69,16 +59,16 @@ class Base4ActionRLEnv(BaseEnvironment):
self._last_trade_tick = None
elif action == Actions.Long_enter.value:
self._position = Positions.Long
- trade_type = "long"
+ trade_type = "enter_long"
self._last_trade_tick = self._current_tick
elif action == Actions.Short_enter.value:
self._position = Positions.Short
- trade_type = "short"
+ trade_type = "enter_short"
self._last_trade_tick = self._current_tick
elif action == Actions.Exit.value:
self._update_total_profit()
self._position = Positions.Neutral
- trade_type = "neutral"
+ trade_type = "exit"
self._last_trade_tick = None
else:
print("case not defined")
@@ -86,7 +76,7 @@ class Base4ActionRLEnv(BaseEnvironment):
if trade_type is not None:
self.trade_history.append(
{'price': self.current_price(), 'index': self._current_tick,
- 'type': trade_type})
+ 'type': trade_type, 'profit': self.get_unrealized_profit()})
if (self._total_profit < self.max_drawdown or
self._total_unrealized_profit < self.max_drawdown):
diff --git a/freqtrade/freqai/RL/Base5ActionRLEnv.py b/freqtrade/freqai/RL/Base5ActionRLEnv.py
index 70053858d..d61c1a393 100644
--- a/freqtrade/freqai/RL/Base5ActionRLEnv.py
+++ b/freqtrade/freqai/RL/Base5ActionRLEnv.py
@@ -53,16 +53,6 @@ class Base5ActionRLEnv(BaseEnvironment):
trade_type = None
if self.is_tradesignal(action):
- """
- Action: Neutral, position: Long -> Close Long
- Action: Neutral, position: Short -> Close Short
-
- Action: Long, position: Neutral -> Open Long
- Action: Long, position: Short -> Close Short and Open Long
-
- Action: Short, position: Neutral -> Open Short
- Action: Short, position: Long -> Close Long and Open Short
- """
if action == Actions.Neutral.value:
self._position = Positions.Neutral
@@ -70,21 +60,21 @@ class Base5ActionRLEnv(BaseEnvironment):
self._last_trade_tick = None
elif action == Actions.Long_enter.value:
self._position = Positions.Long
- trade_type = "long"
+ trade_type = "enter_long"
self._last_trade_tick = self._current_tick
elif action == Actions.Short_enter.value:
self._position = Positions.Short
- trade_type = "short"
+ trade_type = "enter_short"
self._last_trade_tick = self._current_tick
elif action == Actions.Long_exit.value:
self._update_total_profit()
self._position = Positions.Neutral
- trade_type = "neutral"
+ trade_type = "exit_long"
self._last_trade_tick = None
elif action == Actions.Short_exit.value:
self._update_total_profit()
self._position = Positions.Neutral
- trade_type = "neutral"
+ trade_type = "exit_short"
self._last_trade_tick = None
else:
print("case not defined")
@@ -92,7 +82,7 @@ class Base5ActionRLEnv(BaseEnvironment):
if trade_type is not None:
self.trade_history.append(
{'price': self.current_price(), 'index': self._current_tick,
- 'type': trade_type})
+ 'type': trade_type, 'profit': self.get_unrealized_profit()})
if (self._total_profit < self.max_drawdown or
self._total_unrealized_profit < self.max_drawdown):
diff --git a/freqtrade/freqai/base_models/BasePyTorchClassifier.py b/freqtrade/freqai/base_models/BasePyTorchClassifier.py
new file mode 100644
index 000000000..977152cc5
--- /dev/null
+++ b/freqtrade/freqai/base_models/BasePyTorchClassifier.py
@@ -0,0 +1,147 @@
+import logging
+from typing import Dict, List, Tuple
+
+import numpy as np
+import numpy.typing as npt
+import pandas as pd
+import torch
+from pandas import DataFrame
+from torch.nn import functional as F
+
+from freqtrade.exceptions import OperationalException
+from freqtrade.freqai.base_models.BasePyTorchModel import BasePyTorchModel
+from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+
+
+logger = logging.getLogger(__name__)
+
+
+class BasePyTorchClassifier(BasePyTorchModel):
+ """
+ A PyTorch implementation of a classifier.
+ User must implement fit method
+
+ Important!
+
+ - User must declare the target class names in the strategy,
+ under IStrategy.set_freqai_targets method.
+
+ for example, in your strategy:
+ ```
+ def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ self.freqai.class_names = ["down", "up"]
+ dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
+ dataframe["close"], 'up', 'down')
+
+ return dataframe
+ """
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.class_name_to_index = None
+ self.index_to_class_name = None
+
+ def predict(
+ self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
+ ) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
+ """
+ Filter the prediction features data and predict with it.
+ :param unfiltered_df: Full dataframe for the current backtest period.
+ :return:
+ :pred_df: dataframe containing the predictions
+ :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
+ data (NaNs) or felt uncertain about data (PCA and DI index)
+ :raises ValueError: if 'class_names' doesn't exist in model meta_data.
+ """
+
+ class_names = self.model.model_meta_data.get("class_names", None)
+ if not class_names:
+ raise ValueError(
+ "Missing class names. "
+ "self.model.model_meta_data['class_names'] is None."
+ )
+
+ if not self.class_name_to_index:
+ self.init_class_names_to_index_mapping(class_names)
+
+ dk.find_features(unfiltered_df)
+ filtered_df, _ = dk.filter_features(
+ unfiltered_df, dk.training_features_list, training_filter=False
+ )
+ filtered_df = dk.normalize_data_from_metadata(filtered_df)
+ dk.data_dictionary["prediction_features"] = filtered_df
+ self.data_cleaning_predict(dk)
+ x = self.data_convertor.convert_x(
+ dk.data_dictionary["prediction_features"],
+ device=self.device
+ )
+ logits = self.model.model(x)
+ probs = F.softmax(logits, dim=-1)
+ predicted_classes = torch.argmax(probs, dim=-1)
+ predicted_classes_str = self.decode_class_names(predicted_classes)
+ pred_df_prob = DataFrame(probs.detach().numpy(), columns=class_names)
+ pred_df = DataFrame(predicted_classes_str, columns=[dk.label_list[0]])
+ pred_df = pd.concat([pred_df, pred_df_prob], axis=1)
+ return (pred_df, dk.do_predict)
+
+ def encode_class_names(
+ self,
+ data_dictionary: Dict[str, pd.DataFrame],
+ dk: FreqaiDataKitchen,
+ class_names: List[str],
+ ):
+ """
+ encode class name, str -> int
+ assuming first column of *_labels data frame to be the target column
+ containing the class names
+ """
+
+ target_column_name = dk.label_list[0]
+ for split in self.splits:
+ label_df = data_dictionary[f"{split}_labels"]
+ self.assert_valid_class_names(label_df[target_column_name], class_names)
+ label_df[target_column_name] = list(
+ map(lambda x: self.class_name_to_index[x], label_df[target_column_name])
+ )
+
+ @staticmethod
+ def assert_valid_class_names(
+ target_column: pd.Series,
+ class_names: List[str]
+ ):
+ non_defined_labels = set(target_column) - set(class_names)
+ if len(non_defined_labels) != 0:
+ raise OperationalException(
+ f"Found non defined labels: {non_defined_labels}, ",
+ f"expecting labels: {class_names}"
+ )
+
+ def decode_class_names(self, class_ints: torch.Tensor) -> List[str]:
+ """
+ decode class name, int -> str
+ """
+
+ return list(map(lambda x: self.index_to_class_name[x.item()], class_ints))
+
+ def init_class_names_to_index_mapping(self, class_names):
+ self.class_name_to_index = {s: i for i, s in enumerate(class_names)}
+ self.index_to_class_name = {i: s for i, s in enumerate(class_names)}
+ logger.info(f"encoded class name to index: {self.class_name_to_index}")
+
+ def convert_label_column_to_int(
+ self,
+ data_dictionary: Dict[str, pd.DataFrame],
+ dk: FreqaiDataKitchen,
+ class_names: List[str]
+ ):
+ self.init_class_names_to_index_mapping(class_names)
+ self.encode_class_names(data_dictionary, dk, class_names)
+
+ def get_class_names(self) -> List[str]:
+ if not self.class_names:
+ raise ValueError(
+ "self.class_names is empty, "
+ "set self.freqai.class_names = ['class a', 'class b', 'class c'] "
+ "inside IStrategy.set_freqai_targets method."
+ )
+
+ return self.class_names
diff --git a/freqtrade/freqai/base_models/BasePyTorchModel.py b/freqtrade/freqai/base_models/BasePyTorchModel.py
new file mode 100644
index 000000000..8177b8eb8
--- /dev/null
+++ b/freqtrade/freqai/base_models/BasePyTorchModel.py
@@ -0,0 +1,83 @@
+import logging
+from abc import ABC, abstractmethod
+from time import time
+from typing import Any
+
+import torch
+from pandas import DataFrame
+
+from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+from freqtrade.freqai.freqai_interface import IFreqaiModel
+from freqtrade.freqai.torch.PyTorchDataConvertor import PyTorchDataConvertor
+
+
+logger = logging.getLogger(__name__)
+
+
+class BasePyTorchModel(IFreqaiModel, ABC):
+ """
+ Base class for PyTorch type models.
+ User *must* inherit from this class and set fit() and predict() and
+ data_convertor property.
+ """
+
+ def __init__(self, **kwargs):
+ super().__init__(config=kwargs["config"])
+ self.dd.model_type = "pytorch"
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
+ test_size = self.freqai_info.get('data_split_parameters', {}).get('test_size')
+ self.splits = ["train", "test"] if test_size != 0 else ["train"]
+
+ def train(
+ self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
+ ) -> Any:
+ """
+ Filter the training data and train a model to it. Train makes heavy use of the datakitchen
+ for storing, saving, loading, and analyzing the data.
+ :param unfiltered_df: Full dataframe for the current training period
+ :return:
+ :model: Trained model which can be used to inference (self.predict)
+ """
+
+ logger.info(f"-------------------- Starting training {pair} --------------------")
+
+ start_time = time()
+
+ features_filtered, labels_filtered = dk.filter_features(
+ unfiltered_df,
+ dk.training_features_list,
+ dk.label_list,
+ training_filter=True,
+ )
+
+ # split data into train/test data.
+ data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
+ if not self.freqai_info.get("fit_live_predictions", 0) or not self.live:
+ dk.fit_labels()
+ # normalize all data based on train_dataset only
+ data_dictionary = dk.normalize_data(data_dictionary)
+
+ # optional additional data cleaning/analysis
+ self.data_cleaning_train(dk)
+
+ logger.info(
+ f"Training model on {len(dk.data_dictionary['train_features'].columns)} features"
+ )
+ logger.info(f"Training model on {len(data_dictionary['train_features'])} data points")
+
+ model = self.fit(data_dictionary, dk)
+ end_time = time()
+
+ logger.info(f"-------------------- Done training {pair} "
+ f"({end_time - start_time:.2f} secs) --------------------")
+
+ return model
+
+ @property
+ @abstractmethod
+ def data_convertor(self) -> PyTorchDataConvertor:
+ """
+ a class responsible for converting `*_features` & `*_labels` pandas dataframes
+ to pytorch tensors.
+ """
+ raise NotImplementedError("Abstract property")
diff --git a/freqtrade/freqai/base_models/BasePyTorchRegressor.py b/freqtrade/freqai/base_models/BasePyTorchRegressor.py
new file mode 100644
index 000000000..ea6fabe49
--- /dev/null
+++ b/freqtrade/freqai/base_models/BasePyTorchRegressor.py
@@ -0,0 +1,50 @@
+import logging
+from typing import Tuple
+
+import numpy as np
+import numpy.typing as npt
+from pandas import DataFrame
+
+from freqtrade.freqai.base_models.BasePyTorchModel import BasePyTorchModel
+from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+
+
+logger = logging.getLogger(__name__)
+
+
+class BasePyTorchRegressor(BasePyTorchModel):
+ """
+ A PyTorch implementation of a regressor.
+ User must implement fit method
+ """
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def predict(
+ self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
+ ) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
+ """
+ Filter the prediction features data and predict with it.
+ :param unfiltered_df: Full dataframe for the current backtest period.
+ :return:
+ :pred_df: dataframe containing the predictions
+ :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
+ data (NaNs) or felt uncertain about data (PCA and DI index)
+ """
+
+ dk.find_features(unfiltered_df)
+ filtered_df, _ = dk.filter_features(
+ unfiltered_df, dk.training_features_list, training_filter=False
+ )
+ filtered_df = dk.normalize_data_from_metadata(filtered_df)
+ dk.data_dictionary["prediction_features"] = filtered_df
+
+ self.data_cleaning_predict(dk)
+ x = self.data_convertor.convert_x(
+ dk.data_dictionary["prediction_features"],
+ device=self.device
+ )
+ y = self.model.model(x)
+ y = y.cpu()
+ pred_df = DataFrame(y.detach().numpy(), columns=[dk.label_list[0]])
+ return (pred_df, dk.do_predict)
diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py
index 14986d854..b68a9dcad 100644
--- a/freqtrade/freqai/data_drawer.py
+++ b/freqtrade/freqai/data_drawer.py
@@ -446,7 +446,7 @@ class FreqaiDataDrawer:
dump(model, save_path / f"{dk.model_filename}_model.joblib")
elif self.model_type == 'keras':
model.save(save_path / f"{dk.model_filename}_model.h5")
- elif 'stable_baselines' in self.model_type or 'sb3_contrib' == self.model_type:
+ elif self.model_type in ["stable_baselines3", "sb3_contrib", "pytorch"]:
model.save(save_path / f"{dk.model_filename}_model.zip")
if dk.svm_model is not None:
@@ -496,7 +496,7 @@ class FreqaiDataDrawer:
dk.training_features_list = dk.data["training_features_list"]
dk.label_list = dk.data["label_list"]
- def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any:
+ def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any: # noqa: C901
"""
loads all data required to make a prediction on a sub-train time range
:returns:
@@ -537,6 +537,11 @@ class FreqaiDataDrawer:
self.model_type, self.freqai_info['rl_config']['model_type'])
MODELCLASS = getattr(mod, self.freqai_info['rl_config']['model_type'])
model = MODELCLASS.load(dk.data_path / f"{dk.model_filename}_model")
+ elif self.model_type == 'pytorch':
+ import torch
+ zip = torch.load(dk.data_path / f"{dk.model_filename}_model.zip")
+ model = zip["pytrainer"]
+ model = model.load_from_checkpoint(zip)
if Path(dk.data_path / f"{dk.model_filename}_svm_model.joblib").is_file():
dk.svm_model = load(dk.data_path / f"{dk.model_filename}_svm_model.joblib")
diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py
index 52d487b08..21b41db2d 100644
--- a/freqtrade/freqai/data_kitchen.py
+++ b/freqtrade/freqai/data_kitchen.py
@@ -1291,7 +1291,7 @@ class FreqaiDataKitchen:
return dataframe
- def use_strategy_to_populate_indicators(
+ def use_strategy_to_populate_indicators( # noqa: C901
self,
strategy: IStrategy,
corr_dataframes: dict = {},
@@ -1362,12 +1362,12 @@ class FreqaiDataKitchen:
dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy,
corr_dataframes, base_dataframes, True)
- dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
+ if self.live:
+ dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
+ dataframe = self.remove_special_chars_from_feature_names(dataframe)
self.get_unique_classes_from_labels(dataframe)
- dataframe = self.remove_special_chars_from_feature_names(dataframe)
-
if self.config.get('reduce_df_footprint', False):
dataframe = reduce_dataframe_footprint(dataframe)
diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py
index b657bd811..7eaaeab3e 100644
--- a/freqtrade/freqai/freqai_interface.py
+++ b/freqtrade/freqai/freqai_interface.py
@@ -83,6 +83,7 @@ class IFreqaiModel(ABC):
self.CONV_WIDTH = self.freqai_info.get('conv_width', 1)
if self.ft_params.get("inlier_metric_window", 0):
self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2
+ self.class_names: List[str] = [] # used in classification subclasses
self.pair_it = 0
self.pair_it_train = 0
self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist"))
@@ -306,7 +307,7 @@ class IFreqaiModel(ABC):
if check_features:
self.dd.load_metadata(dk)
dataframe_dummy_features = self.dk.use_strategy_to_populate_indicators(
- strategy, prediction_dataframe=dataframe.tail(1), pair=metadata["pair"]
+ strategy, prediction_dataframe=dataframe.tail(1), pair=pair
)
dk.find_features(dataframe_dummy_features)
self.check_if_feature_list_matches_strategy(dk)
@@ -316,7 +317,7 @@ class IFreqaiModel(ABC):
else:
if populate_indicators:
dataframe = self.dk.use_strategy_to_populate_indicators(
- strategy, prediction_dataframe=dataframe, pair=metadata["pair"]
+ strategy, prediction_dataframe=dataframe, pair=pair
)
populate_indicators = False
@@ -332,6 +333,10 @@ class IFreqaiModel(ABC):
dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train)
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest)
+ dataframe_train = dk.remove_special_chars_from_feature_names(dataframe_train)
+ dataframe_backtest = dk.remove_special_chars_from_feature_names(dataframe_backtest)
+ dk.get_unique_classes_from_labels(dataframe_train)
+
if not self.model_exists(dk):
dk.find_features(dataframe_train)
dk.find_labels(dataframe_train)
@@ -567,8 +572,9 @@ class IFreqaiModel(ABC):
file_type = ".joblib"
elif self.dd.model_type == 'keras':
file_type = ".h5"
- elif 'stable_baselines' in self.dd.model_type or 'sb3_contrib' == self.dd.model_type:
+ elif self.dd.model_type in ["stable_baselines3", "sb3_contrib", "pytorch"]:
file_type = ".zip"
+
path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model{file_type}")
file_exists = path_to_modelfile.is_file()
if file_exists:
diff --git a/freqtrade/freqai/prediction_models/CatboostClassifier.py b/freqtrade/freqai/prediction_models/CatboostClassifier.py
index ca1d8ece0..b9904e40d 100644
--- a/freqtrade/freqai/prediction_models/CatboostClassifier.py
+++ b/freqtrade/freqai/prediction_models/CatboostClassifier.py
@@ -14,16 +14,20 @@ logger = logging.getLogger(__name__)
class CatboostClassifier(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
diff --git a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py
index c6f900fad..58c47566a 100644
--- a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py
+++ b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py
@@ -15,16 +15,20 @@ logger = logging.getLogger(__name__)
class CatboostClassifierMultiTarget(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
cbc = CatBoostClassifier(
diff --git a/freqtrade/freqai/prediction_models/CatboostRegressor.py b/freqtrade/freqai/prediction_models/CatboostRegressor.py
index 4b17a703b..28b1b11cc 100644
--- a/freqtrade/freqai/prediction_models/CatboostRegressor.py
+++ b/freqtrade/freqai/prediction_models/CatboostRegressor.py
@@ -14,16 +14,20 @@ logger = logging.getLogger(__name__)
class CatboostRegressor(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
diff --git a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py
index 976d0b29b..1562c2024 100644
--- a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py
+++ b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py
@@ -15,16 +15,20 @@ logger = logging.getLogger(__name__)
class CatboostRegressorMultiTarget(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
cbr = CatBoostRegressor(
diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifier.py b/freqtrade/freqai/prediction_models/LightGBMClassifier.py
index e467ad3c1..45f3a31d0 100644
--- a/freqtrade/freqai/prediction_models/LightGBMClassifier.py
+++ b/freqtrade/freqai/prediction_models/LightGBMClassifier.py
@@ -12,16 +12,20 @@ logger = logging.getLogger(__name__)
class LightGBMClassifier(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) == 0:
diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py
index d1eb6daa2..72a8ee259 100644
--- a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py
+++ b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py
@@ -13,16 +13,20 @@ logger = logging.getLogger(__name__)
class LightGBMClassifierMultiTarget(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
lgb = LGBMClassifier(**self.model_training_parameters)
diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressor.py b/freqtrade/freqai/prediction_models/LightGBMRegressor.py
index 85c9b691c..3d1c30ed3 100644
--- a/freqtrade/freqai/prediction_models/LightGBMRegressor.py
+++ b/freqtrade/freqai/prediction_models/LightGBMRegressor.py
@@ -12,18 +12,20 @@ logger = logging.getLogger(__name__)
class LightGBMRegressor(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
- Most regressors use the same function names and arguments e.g. user
- can drop in LGBMRegressor in place of CatBoostRegressor and all data
- management will be properly handled by Freqai.
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ User sets up the training and test data to fit their desired model here
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) == 0:
diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py
index 37c6bb186..663a611f0 100644
--- a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py
+++ b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py
@@ -13,16 +13,20 @@ logger = logging.getLogger(__name__)
class LightGBMRegressorMultiTarget(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
lgb = LGBMRegressor(**self.model_training_parameters)
diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py
new file mode 100644
index 000000000..ea7981405
--- /dev/null
+++ b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py
@@ -0,0 +1,89 @@
+from typing import Any, Dict
+
+import torch
+
+from freqtrade.freqai.base_models.BasePyTorchClassifier import BasePyTorchClassifier
+from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+from freqtrade.freqai.torch.PyTorchDataConvertor import (DefaultPyTorchDataConvertor,
+ PyTorchDataConvertor)
+from freqtrade.freqai.torch.PyTorchMLPModel import PyTorchMLPModel
+from freqtrade.freqai.torch.PyTorchModelTrainer import PyTorchModelTrainer
+
+
+class PyTorchMLPClassifier(BasePyTorchClassifier):
+ """
+ This class implements the fit method of IFreqaiModel.
+ in the fit method we initialize the model and trainer objects.
+ the only requirement from the model is to be aligned to PyTorchClassifier
+ predict method that expects the model to predict a tensor of type long.
+
+ parameters are passed via `model_training_parameters` under the freqai
+ section in the config file. e.g:
+ {
+ ...
+ "freqai": {
+ ...
+ "model_training_parameters" : {
+ "learning_rate": 3e-4,
+ "trainer_kwargs": {
+ "max_iters": 5000,
+ "batch_size": 64,
+ "max_n_eval_batches": null,
+ },
+ "model_kwargs": {
+ "hidden_dim": 512,
+ "dropout_percent": 0.2,
+ "n_layer": 1,
+ },
+ }
+ }
+ }
+ """
+
+ @property
+ def data_convertor(self) -> PyTorchDataConvertor:
+ return DefaultPyTorchDataConvertor(
+ target_tensor_type=torch.long,
+ squeeze_target_tensor=True
+ )
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ config = self.freqai_info.get("model_training_parameters", {})
+ self.learning_rate: float = config.get("learning_rate", 3e-4)
+ self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+
+ def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ """
+ User sets up the training and test data to fit their desired model here
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
+ :raises ValueError: If self.class_names is not defined in the parent class.
+ """
+
+ class_names = self.get_class_names()
+ self.convert_label_column_to_int(data_dictionary, dk, class_names)
+ n_features = data_dictionary["train_features"].shape[-1]
+ model = PyTorchMLPModel(
+ input_dim=n_features,
+ output_dim=len(class_names),
+ **self.model_kwargs
+ )
+ model.to(self.device)
+ optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
+ criterion = torch.nn.CrossEntropyLoss()
+ init_model = self.get_init_model(dk.pair)
+ trainer = PyTorchModelTrainer(
+ model=model,
+ optimizer=optimizer,
+ criterion=criterion,
+ model_meta_data={"class_names": class_names},
+ device=self.device,
+ init_model=init_model,
+ data_convertor=self.data_convertor,
+ **self.trainer_kwargs,
+ )
+ trainer.fit(data_dictionary, self.splits)
+ return trainer
diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py
new file mode 100644
index 000000000..64f0f4b03
--- /dev/null
+++ b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py
@@ -0,0 +1,83 @@
+from typing import Any, Dict
+
+import torch
+
+from freqtrade.freqai.base_models.BasePyTorchRegressor import BasePyTorchRegressor
+from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+from freqtrade.freqai.torch.PyTorchDataConvertor import (DefaultPyTorchDataConvertor,
+ PyTorchDataConvertor)
+from freqtrade.freqai.torch.PyTorchMLPModel import PyTorchMLPModel
+from freqtrade.freqai.torch.PyTorchModelTrainer import PyTorchModelTrainer
+
+
+class PyTorchMLPRegressor(BasePyTorchRegressor):
+ """
+ This class implements the fit method of IFreqaiModel.
+ in the fit method we initialize the model and trainer objects.
+ the only requirement from the model is to be aligned to PyTorchRegressor
+ predict method that expects the model to predict tensor of type float.
+ the trainer defines the training loop.
+
+ parameters are passed via `model_training_parameters` under the freqai
+ section in the config file. e.g:
+ {
+ ...
+ "freqai": {
+ ...
+ "model_training_parameters" : {
+ "learning_rate": 3e-4,
+ "trainer_kwargs": {
+ "max_iters": 5000,
+ "batch_size": 64,
+ "max_n_eval_batches": null,
+ },
+ "model_kwargs": {
+ "hidden_dim": 512,
+ "dropout_percent": 0.2,
+ "n_layer": 1,
+ },
+ }
+ }
+ }
+ """
+
+ @property
+ def data_convertor(self) -> PyTorchDataConvertor:
+ return DefaultPyTorchDataConvertor(target_tensor_type=torch.float)
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ config = self.freqai_info.get("model_training_parameters", {})
+ self.learning_rate: float = config.get("learning_rate", 3e-4)
+ self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+
+ def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ """
+ User sets up the training and test data to fit their desired model here
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
+ """
+
+ n_features = data_dictionary["train_features"].shape[-1]
+ model = PyTorchMLPModel(
+ input_dim=n_features,
+ output_dim=1,
+ **self.model_kwargs
+ )
+ model.to(self.device)
+ optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
+ criterion = torch.nn.MSELoss()
+ init_model = self.get_init_model(dk.pair)
+ trainer = PyTorchModelTrainer(
+ model=model,
+ optimizer=optimizer,
+ criterion=criterion,
+ device=self.device,
+ init_model=init_model,
+ data_convertor=self.data_convertor,
+ **self.trainer_kwargs,
+ )
+ trainer.fit(data_dictionary, self.splits)
+ return trainer
diff --git a/freqtrade/freqai/prediction_models/XGBoostClassifier.py b/freqtrade/freqai/prediction_models/XGBoostClassifier.py
index 67c7c7783..b6f04b497 100644
--- a/freqtrade/freqai/prediction_models/XGBoostClassifier.py
+++ b/freqtrade/freqai/prediction_models/XGBoostClassifier.py
@@ -18,16 +18,20 @@ logger = logging.getLogger(__name__)
class XGBoostClassifier(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
X = data_dictionary["train_features"].to_numpy()
diff --git a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py
index 470c283ea..20156e9fd 100644
--- a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py
+++ b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py
@@ -18,16 +18,20 @@ logger = logging.getLogger(__name__)
class XGBoostRFClassifier(BaseClassifierModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
X = data_dictionary["train_features"].to_numpy()
diff --git a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py
index e7cc27f2e..1aefbf19a 100644
--- a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py
+++ b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py
@@ -12,16 +12,20 @@ logger = logging.getLogger(__name__)
class XGBoostRFRegressor(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
X = data_dictionary["train_features"]
diff --git a/freqtrade/freqai/prediction_models/XGBoostRegressor.py b/freqtrade/freqai/prediction_models/XGBoostRegressor.py
index 9a280286b..93dfb319e 100644
--- a/freqtrade/freqai/prediction_models/XGBoostRegressor.py
+++ b/freqtrade/freqai/prediction_models/XGBoostRegressor.py
@@ -12,16 +12,20 @@ logger = logging.getLogger(__name__)
class XGBoostRegressor(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
X = data_dictionary["train_features"]
diff --git a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py
index 920745ec9..a0330485e 100644
--- a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py
+++ b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py
@@ -13,16 +13,20 @@ logger = logging.getLogger(__name__)
class XGBoostRegressorMultiTarget(BaseRegressionModel):
"""
- User created prediction model. The class needs to override three necessary
- functions, predict(), train(), fit(). The class inherits ModelHandler which
- has its own DataHandler where data is held, saved, loaded, and managed.
+ User created prediction model. The class inherits IFreqaiModel, which
+ means it has full access to all Frequency AI functionality. Typically,
+ users would use this to override the common `fit()`, `train()`, or
+ `predict()` methods to add their custom data handling tools or change
+ various aspects of the training that cannot be configured via the
+ top level config.json file.
"""
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
- :param data_dictionary: the dictionary constructed by DataHandler to hold
- all the training and test data/labels.
+ :param data_dictionary: the dictionary holding all data for train, test,
+ labels, weights
+ :param dk: The datakitchen object for the current coin/model
"""
xgb = XGBRegressor(**self.model_training_parameters)
diff --git a/freqtrade/freqai/torch/PyTorchDataConvertor.py b/freqtrade/freqai/torch/PyTorchDataConvertor.py
new file mode 100644
index 000000000..a31ccdc79
--- /dev/null
+++ b/freqtrade/freqai/torch/PyTorchDataConvertor.py
@@ -0,0 +1,67 @@
+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+import pandas as pd
+import torch
+
+
+class PyTorchDataConvertor(ABC):
+ """
+ This class is responsible for converting `*_features` & `*_labels` pandas dataframes
+ to pytorch tensors.
+ """
+
+ @abstractmethod
+ def convert_x(self, df: pd.DataFrame, device: Optional[str] = None) -> List[torch.Tensor]:
+ """
+ :param df: "*_features" dataframe.
+ :param device: The device to use for training (e.g. 'cpu', 'cuda').
+ """
+
+ @abstractmethod
+ def convert_y(self, df: pd.DataFrame, device: Optional[str] = None) -> List[torch.Tensor]:
+ """
+ :param df: "*_labels" dataframe.
+ :param device: The device to use for training (e.g. 'cpu', 'cuda').
+ """
+
+
+class DefaultPyTorchDataConvertor(PyTorchDataConvertor):
+ """
+ A default conversion that keeps features dataframe shapes.
+ """
+
+ def __init__(
+ self,
+ target_tensor_type: Optional[torch.dtype] = None,
+ squeeze_target_tensor: bool = False
+ ):
+ """
+ :param target_tensor_type: type of target tensor, for classification use
+ torch.long, for regressor use torch.float or torch.double.
+ :param squeeze_target_tensor: controls the target shape, used for loss functions
+ that requires 0D or 1D.
+ """
+ self._target_tensor_type = target_tensor_type
+ self._squeeze_target_tensor = squeeze_target_tensor
+
+ def convert_x(self, df: pd.DataFrame, device: Optional[str] = None) -> List[torch.Tensor]:
+ x = torch.from_numpy(df.values).float()
+ if device:
+ x = x.to(device)
+
+ return [x]
+
+ def convert_y(self, df: pd.DataFrame, device: Optional[str] = None) -> List[torch.Tensor]:
+ y = torch.from_numpy(df.values)
+
+ if self._target_tensor_type:
+ y = y.to(self._target_tensor_type)
+
+ if self._squeeze_target_tensor:
+ y = y.squeeze()
+
+ if device:
+ y = y.to(device)
+
+ return [y]
diff --git a/freqtrade/freqai/torch/PyTorchMLPModel.py b/freqtrade/freqai/torch/PyTorchMLPModel.py
new file mode 100644
index 000000000..62d3216df
--- /dev/null
+++ b/freqtrade/freqai/torch/PyTorchMLPModel.py
@@ -0,0 +1,97 @@
+import logging
+from typing import List
+
+import torch
+from torch import nn
+
+
+logger = logging.getLogger(__name__)
+
+
+class PyTorchMLPModel(nn.Module):
+ """
+ A multi-layer perceptron (MLP) model implemented using PyTorch.
+
+ This class mainly serves as a simple example for the integration of PyTorch model's
+ to freqai. It is not optimized at all and should not be used for production purposes.
+
+ :param input_dim: The number of input features. This parameter specifies the number
+ of features in the input data that the MLP will use to make predictions.
+ :param output_dim: The number of output classes. This parameter specifies the number
+ of classes that the MLP will predict.
+ :param hidden_dim: The number of hidden units in each layer. This parameter controls
+ the complexity of the MLP and determines how many nonlinear relationships the MLP
+ can represent. Increasing the number of hidden units can increase the capacity of
+ the MLP to model complex patterns, but it also increases the risk of overfitting
+ the training data. Default: 256
+ :param dropout_percent: The dropout rate for regularization. This parameter specifies
+ the probability of dropping out a neuron during training to prevent overfitting.
+ The dropout rate should be tuned carefully to balance between underfitting and
+ overfitting. Default: 0.2
+ :param n_layer: The number of layers in the MLP. This parameter specifies the number
+ of layers in the MLP architecture. Adding more layers to the MLP can increase its
+ capacity to model complex patterns, but it also increases the risk of overfitting
+ the training data. Default: 1
+
+ :returns: The output of the MLP, with shape (batch_size, output_dim)
+ """
+
+ def __init__(self, input_dim: int, output_dim: int, **kwargs):
+ super().__init__()
+ hidden_dim: int = kwargs.get("hidden_dim", 256)
+ dropout_percent: int = kwargs.get("dropout_percent", 0.2)
+ n_layer: int = kwargs.get("n_layer", 1)
+ self.input_layer = nn.Linear(input_dim, hidden_dim)
+ self.blocks = nn.Sequential(*[Block(hidden_dim, dropout_percent) for _ in range(n_layer)])
+ self.output_layer = nn.Linear(hidden_dim, output_dim)
+ self.relu = nn.ReLU()
+ self.dropout = nn.Dropout(p=dropout_percent)
+
+ def forward(self, tensors: List[torch.Tensor]) -> torch.Tensor:
+ x: torch.Tensor = tensors[0]
+ x = self.relu(self.input_layer(x))
+ x = self.dropout(x)
+ x = self.blocks(x)
+ x = self.output_layer(x)
+ return x
+
+
+class Block(nn.Module):
+ """
+ A building block for a multi-layer perceptron (MLP).
+
+ :param hidden_dim: The number of hidden units in the feedforward network.
+ :param dropout_percent: The dropout rate for regularization.
+
+ :returns: torch.Tensor. with shape (batch_size, hidden_dim)
+ """
+
+ def __init__(self, hidden_dim: int, dropout_percent: int):
+ super().__init__()
+ self.ff = FeedForward(hidden_dim)
+ self.dropout = nn.Dropout(p=dropout_percent)
+ self.ln = nn.LayerNorm(hidden_dim)
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.ff(self.ln(x))
+ x = self.dropout(x)
+ return x
+
+
+class FeedForward(nn.Module):
+ """
+ A simple fully-connected feedforward neural network block.
+
+ :param hidden_dim: The number of hidden units in the block.
+ :return: torch.Tensor. with shape (batch_size, hidden_dim)
+ """
+
+ def __init__(self, hidden_dim: int):
+ super().__init__()
+ self.net = nn.Sequential(
+ nn.Linear(hidden_dim, hidden_dim),
+ nn.ReLU(),
+ )
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ return self.net(x)
diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py
new file mode 100644
index 000000000..8277ba937
--- /dev/null
+++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py
@@ -0,0 +1,208 @@
+import logging
+import math
+from pathlib import Path
+from typing import Any, Dict, List, Optional
+
+import pandas as pd
+import torch
+from torch import nn
+from torch.optim import Optimizer
+from torch.utils.data import DataLoader, TensorDataset
+
+from freqtrade.freqai.torch.PyTorchDataConvertor import PyTorchDataConvertor
+from freqtrade.freqai.torch.PyTorchTrainerInterface import PyTorchTrainerInterface
+
+
+logger = logging.getLogger(__name__)
+
+
+class PyTorchModelTrainer(PyTorchTrainerInterface):
+ def __init__(
+ self,
+ model: nn.Module,
+ optimizer: Optimizer,
+ criterion: nn.Module,
+ device: str,
+ init_model: Dict,
+ data_convertor: PyTorchDataConvertor,
+ model_meta_data: Dict[str, Any] = {},
+ **kwargs
+ ):
+ """
+ :param model: The PyTorch model to be trained.
+ :param optimizer: The optimizer to use for training.
+ :param criterion: The loss function to use for training.
+ :param device: The device to use for training (e.g. 'cpu', 'cuda').
+ :param init_model: A dictionary containing the initial model/optimizer
+ state_dict and model_meta_data saved by self.save() method.
+ :param model_meta_data: Additional metadata about the model (optional).
+ :param data_convertor: convertor from pd.DataFrame to torch.tensor.
+ :param max_iters: The number of training iterations to run.
+ iteration here refers to the number of times we call
+ self.optimizer.step(). used to calculate n_epochs.
+ :param batch_size: The size of the batches to use during training.
+ :param max_n_eval_batches: The maximum number batches to use for evaluation.
+ """
+ self.model = model
+ self.optimizer = optimizer
+ self.criterion = criterion
+ self.model_meta_data = model_meta_data
+ self.device = device
+ self.max_iters: int = kwargs.get("max_iters", 100)
+ self.batch_size: int = kwargs.get("batch_size", 64)
+ self.max_n_eval_batches: Optional[int] = kwargs.get("max_n_eval_batches", None)
+ self.data_convertor = data_convertor
+ if init_model:
+ self.load_from_checkpoint(init_model)
+
+ def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]):
+ """
+ :param data_dictionary: the dictionary constructed by DataHandler to hold
+ all the training and test data/labels.
+ :param splits: splits to use in training, splits must contain "train",
+ optional "test" could be added by setting freqai.data_split_parameters.test_size > 0
+ in the config file.
+
+ - Calculates the predicted output for the batch using the PyTorch model.
+ - Calculates the loss between the predicted and actual output using a loss function.
+ - Computes the gradients of the loss with respect to the model's parameters using
+ backpropagation.
+ - Updates the model's parameters using an optimizer.
+ """
+ data_loaders_dictionary = self.create_data_loaders_dictionary(data_dictionary, splits)
+ epochs = self.calc_n_epochs(
+ n_obs=len(data_dictionary["train_features"]),
+ batch_size=self.batch_size,
+ n_iters=self.max_iters
+ )
+ for epoch in range(1, epochs + 1):
+ # training
+ losses = []
+ for i, batch_data in enumerate(data_loaders_dictionary["train"]):
+
+ for tensor in batch_data:
+ tensor.to(self.device)
+
+ xb = batch_data[:-1]
+ yb = batch_data[-1]
+ yb_pred = self.model(xb)
+ loss = self.criterion(yb_pred, yb)
+
+ self.optimizer.zero_grad(set_to_none=True)
+ loss.backward()
+ self.optimizer.step()
+ losses.append(loss.item())
+ train_loss = sum(losses) / len(losses)
+ log_message = f"epoch {epoch}/{epochs}: train loss {train_loss:.4f}"
+
+ # evaluation
+ if "test" in splits:
+ test_loss = self.estimate_loss(
+ data_loaders_dictionary,
+ self.max_n_eval_batches,
+ "test"
+ )
+ log_message += f" ; test loss {test_loss:.4f}"
+
+ logger.info(log_message)
+
+ @torch.no_grad()
+ def estimate_loss(
+ self,
+ data_loader_dictionary: Dict[str, DataLoader],
+ max_n_eval_batches: Optional[int],
+ split: str,
+ ) -> float:
+ self.model.eval()
+ n_batches = 0
+ losses = []
+ for i, batch_data in enumerate(data_loader_dictionary[split]):
+ if max_n_eval_batches and i > max_n_eval_batches:
+ n_batches += 1
+ break
+
+ for tensor in batch_data:
+ tensor.to(self.device)
+
+ xb = batch_data[:-1]
+ yb = batch_data[-1]
+ yb_pred = self.model(xb)
+ loss = self.criterion(yb_pred, yb)
+ losses.append(loss.item())
+
+ self.model.train()
+ return sum(losses) / len(losses)
+
+ def create_data_loaders_dictionary(
+ self,
+ data_dictionary: Dict[str, pd.DataFrame],
+ splits: List[str]
+ ) -> Dict[str, DataLoader]:
+ """
+ Converts the input data to PyTorch tensors using a data loader.
+ """
+ data_loader_dictionary = {}
+ for split in splits:
+ x = self.data_convertor.convert_x(data_dictionary[f"{split}_features"], self.device)
+ y = self.data_convertor.convert_y(data_dictionary[f"{split}_labels"], self.device)
+ dataset = TensorDataset(*x, *y)
+ data_loader = DataLoader(
+ dataset,
+ batch_size=self.batch_size,
+ shuffle=True,
+ drop_last=True,
+ num_workers=0,
+ )
+ data_loader_dictionary[split] = data_loader
+
+ return data_loader_dictionary
+
+ @staticmethod
+ def calc_n_epochs(n_obs: int, batch_size: int, n_iters: int) -> int:
+ """
+ Calculates the number of epochs required to reach the maximum number
+ of iterations specified in the model training parameters.
+
+ the motivation here is that `max_iters` is easier to optimize and keep stable,
+ across different n_obs - the number of data points.
+ """
+
+ n_batches = math.ceil(n_obs // batch_size)
+ epochs = math.ceil(n_iters // n_batches)
+ if epochs <= 10:
+ logger.warning("User set `max_iters` in such a way that the trainer will only perform "
+ f" {epochs} epochs. Please consider increasing this value accordingly")
+ if epochs <= 1:
+ logger.warning("Epochs set to 1. Please review your `max_iters` value")
+ epochs = 1
+ return epochs
+
+ def save(self, path: Path):
+ """
+ - Saving any nn.Module state_dict
+ - Saving model_meta_data, this dict should contain any additional data that the
+ user needs to store. e.g class_names for classification models.
+ """
+
+ torch.save({
+ "model_state_dict": self.model.state_dict(),
+ "optimizer_state_dict": self.optimizer.state_dict(),
+ "model_meta_data": self.model_meta_data,
+ "pytrainer": self
+ }, path)
+
+ def load(self, path: Path):
+ checkpoint = torch.load(path)
+ return self.load_from_checkpoint(checkpoint)
+
+ def load_from_checkpoint(self, checkpoint: Dict):
+ """
+ when using continual_learning, DataDrawer will load the dictionary
+ (containing state dicts and model_meta_data) by calling torch.load(path).
+ you can access this dict from any class that inherits IFreqaiModel by calling
+ get_init_model method.
+ """
+ self.model.load_state_dict(checkpoint["model_state_dict"])
+ self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
+ self.model_meta_data = checkpoint["model_meta_data"]
+ return self
diff --git a/freqtrade/freqai/torch/PyTorchTrainerInterface.py b/freqtrade/freqai/torch/PyTorchTrainerInterface.py
new file mode 100644
index 000000000..840c145f7
--- /dev/null
+++ b/freqtrade/freqai/torch/PyTorchTrainerInterface.py
@@ -0,0 +1,53 @@
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import Dict, List
+
+import pandas as pd
+import torch
+from torch import nn
+
+
+class PyTorchTrainerInterface(ABC):
+
+ @abstractmethod
+ def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]) -> None:
+ """
+ :param data_dictionary: the dictionary constructed by DataHandler to hold
+ all the training and test data/labels.
+ :param splits: splits to use in training, splits must contain "train",
+ optional "test" could be added by setting freqai.data_split_parameters.test_size > 0
+ in the config file.
+
+ - Calculates the predicted output for the batch using the PyTorch model.
+ - Calculates the loss between the predicted and actual output using a loss function.
+ - Computes the gradients of the loss with respect to the model's parameters using
+ backpropagation.
+ - Updates the model's parameters using an optimizer.
+ """
+
+ @abstractmethod
+ def save(self, path: Path) -> None:
+ """
+ - Saving any nn.Module state_dict
+ - Saving model_meta_data, this dict should contain any additional data that the
+ user needs to store. e.g class_names for classification models.
+ """
+
+ def load(self, path: Path) -> nn.Module:
+ """
+ :param path: path to zip file.
+ :returns: pytorch model.
+ """
+ checkpoint = torch.load(path)
+ return self.load_from_checkpoint(checkpoint)
+
+ @abstractmethod
+ def load_from_checkpoint(self, checkpoint: Dict) -> nn.Module:
+ """
+ when using continual_learning, DataDrawer will load the dictionary
+ (containing state dicts and model_meta_data) by calling torch.load(path).
+ you can access this dict from any class that inherits IFreqaiModel by calling
+ get_init_model method.
+ :checkpoint checkpoint: dict containing the model & optimizer state dicts,
+ model_meta_data, etc..
+ """
diff --git a/freqtrade/freqai/torch/__init__.py b/freqtrade/freqai/torch/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 42db121e9..73b25a7a1 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -21,10 +21,12 @@ from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode,
State, TradingMode)
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
InvalidOrderException, PricingError)
-from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
+from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, timeframe_to_minutes, timeframe_to_next_date,
+ timeframe_to_seconds)
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin
from freqtrade.persistence import Order, PairLocks, Trade, init_db
+from freqtrade.persistence.key_value_store import set_startup_time
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@@ -181,6 +183,7 @@ class FreqtradeBot(LoggingMixin):
performs startup tasks
"""
migrate_binance_futures_names(self.config)
+ set_startup_time()
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
# Update older trades with precision and precision mode
@@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Canceling stoploss on exchange for {trade}")
co = self.exchange.cancel_stoploss_order_with_result(
trade.stoploss_order_id, trade.pair, trade.amount)
- trade.update_order(co)
+ self.update_trade_state(trade, trade.stoploss_order_id, co, stoploss_order=True)
+
# Reset stoploss order id.
trade.stoploss_order_id = None
except InvalidOrderException:
@@ -945,7 +949,7 @@ class FreqtradeBot(LoggingMixin):
return enter_limit_requested, stake_amount, leverage
- def _notify_enter(self, trade: Trade, order: Order, order_type: Optional[str] = None,
+ def _notify_enter(self, trade: Trade, order: Order, order_type: str,
fill: bool = False, sub_trade: bool = False) -> None:
"""
Sends rpc notification when a entry order occurred.
@@ -1171,7 +1175,8 @@ class FreqtradeBot(LoggingMixin):
logger.warning('Unable to fetch stoploss order: %s', exception)
if stoploss_order:
- trade.update_order(stoploss_order)
+ self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
+ stoploss_order=True)
# We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
@@ -1235,7 +1240,9 @@ class FreqtradeBot(LoggingMixin):
:param order: Current on exchange stoploss order
:return: None
"""
- stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation)
+ stoploss_norm = self.exchange.price_to_precision(
+ trade.pair, trade.stoploss_or_liquidation,
+ rounding_mode=ROUND_DOWN if trade.is_short else ROUND_UP)
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
# we check if the update is necessary
@@ -1478,8 +1485,8 @@ class FreqtradeBot(LoggingMixin):
return False
try:
- order = self.exchange.cancel_order_with_result(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}")
@@ -1491,17 +1498,18 @@ class FreqtradeBot(LoggingMixin):
# Order might be filled above in odd timing issues.
if order.get('status') in ('canceled', 'cancelled'):
trade.exit_reason = None
+ trade.open_order_id = None
else:
trade.exit_reason = exit_reason_prev
cancelled = True
else:
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
trade.exit_reason = None
+ trade.open_order_id = 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
@@ -1778,11 +1786,11 @@ class FreqtradeBot(LoggingMixin):
return False
# Update trade with order values
- logger.info(f'Found open order for {trade}')
+ if not stoploss_order:
+ logger.info(f'Found open order for {trade}')
try:
- order = action_order or self.exchange.fetch_order_or_stoploss_order(order_id,
- trade.pair,
- stoploss_order)
+ order = action_order or self.exchange.fetch_order_or_stoploss_order(
+ order_id, trade.pair, stoploss_order)
except InvalidOrderException as exception:
logger.warning('Unable to fetch order %s: %s', order_id, exception)
return False
@@ -1811,7 +1819,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()
@@ -1847,7 +1855,7 @@ class FreqtradeBot(LoggingMixin):
self.handle_protections(trade.pair, trade.trade_direction)
elif send_msg and not trade.open_order_id and not stoploss_order:
# Enter fill
- self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
+ self._notify_enter(trade, order, order.order_type, fill=True, sub_trade=sub_trade)
def handle_protections(self, pair: str, side: LongShort) -> None:
# Lock pair for one candle to prevent immediate rebuys
diff --git a/freqtrade/loggers.py b/freqtrade/loggers/__init__.py
similarity index 88%
rename from freqtrade/loggers.py
rename to freqtrade/loggers/__init__.py
index 823fa174e..528d274f2 100644
--- a/freqtrade/loggers.py
+++ b/freqtrade/loggers/__init__.py
@@ -1,24 +1,11 @@
import logging
-import sys
from logging import Formatter
-from logging.handlers import BufferingHandler, RotatingFileHandler, SysLogHandler
+from logging.handlers import RotatingFileHandler, SysLogHandler
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
-
-
-class FTBufferingHandler(BufferingHandler):
- def flush(self):
- """
- Override Flush behaviour - we keep half of the configured capacity
- otherwise, we have moments with "empty" logs.
- """
- self.acquire()
- try:
- # Keep half of the records in buffer.
- self.buffer = self.buffer[-int(self.capacity / 2):]
- finally:
- self.release()
+from freqtrade.loggers.buffering_handler import FTBufferingHandler
+from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler
logger = logging.getLogger(__name__)
@@ -69,7 +56,7 @@ def setup_logging_pre() -> None:
logging.basicConfig(
level=logging.INFO,
format=LOGFORMAT,
- handlers=[logging.StreamHandler(sys.stderr), bufferHandler]
+ handlers=[FTStdErrStreamHandler(), bufferHandler]
)
diff --git a/freqtrade/loggers/buffering_handler.py b/freqtrade/loggers/buffering_handler.py
new file mode 100644
index 000000000..e4621fa79
--- /dev/null
+++ b/freqtrade/loggers/buffering_handler.py
@@ -0,0 +1,15 @@
+from logging.handlers import BufferingHandler
+
+
+class FTBufferingHandler(BufferingHandler):
+ def flush(self):
+ """
+ Override Flush behaviour - we keep half of the configured capacity
+ otherwise, we have moments with "empty" logs.
+ """
+ self.acquire()
+ try:
+ # Keep half of the records in buffer.
+ self.buffer = self.buffer[-int(self.capacity / 2):]
+ finally:
+ self.release()
diff --git a/freqtrade/loggers/std_err_stream_handler.py b/freqtrade/loggers/std_err_stream_handler.py
new file mode 100644
index 000000000..487a7c100
--- /dev/null
+++ b/freqtrade/loggers/std_err_stream_handler.py
@@ -0,0 +1,26 @@
+import sys
+from logging import Handler
+
+
+class FTStdErrStreamHandler(Handler):
+ def flush(self):
+ """
+ Override Flush behaviour - we keep half of the configured capacity
+ otherwise, we have moments with "empty" logs.
+ """
+ self.acquire()
+ try:
+ sys.stderr.flush()
+ finally:
+ self.release()
+
+ def emit(self, record):
+ try:
+ msg = self.format(record)
+ # Don't keep a reference to stderr - this can be problematic with progressbars.
+ sys.stderr.write(msg + '\n')
+ self.flush()
+ except RecursionError:
+ raise
+ except Exception:
+ self.handleError(record)
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index 96c95c4a2..ee5599e20 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -13,13 +13,13 @@ from math import ceil
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
-import progressbar
import rapidjson
-from colorama import Fore, Style
from colorama import init as colorama_init
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
from joblib.externals import cloudpickle
from pandas import DataFrame
+from rich.progress import (BarColumn, MofNCompleteColumn, Progress, TaskProgressColumn, TextColumn,
+ TimeElapsedColumn, TimeRemainingColumn)
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
from freqtrade.data.converter import trim_dataframes
@@ -44,8 +44,6 @@ with warnings.catch_warnings():
from skopt import Optimizer
from skopt.space import Dimension
-progressbar.streams.wrap_stderr()
-progressbar.streams.wrap_stdout()
logger = logging.getLogger(__name__)
@@ -520,29 +518,6 @@ class Hyperopt:
else:
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
- def get_progressbar_widgets(self):
- if self.print_colorized:
- widgets = [
- ' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
- ' (', progressbar.Percentage(), ')] ',
- progressbar.Bar(marker=progressbar.AnimatedMarker(
- fill='\N{FULL BLOCK}',
- fill_wrap=Fore.GREEN + '{}' + Fore.RESET,
- marker_wrap=Style.BRIGHT + '{}' + Style.RESET_ALL,
- )),
- ' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
- ]
- else:
- widgets = [
- ' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
- ' (', progressbar.Percentage(), ')] ',
- progressbar.Bar(marker=progressbar.AnimatedMarker(
- fill='\N{FULL BLOCK}',
- )),
- ' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
- ]
- return widgets
-
def evaluate_result(self, val: Dict[str, Any], current: int, is_random: bool):
"""
Evaluate results returned from generate_optimizer
@@ -602,11 +577,19 @@ class Hyperopt:
logger.info(f'Effective number of parallel workers used: {jobs}')
# Define progressbar
- widgets = self.get_progressbar_widgets()
- with progressbar.ProgressBar(
- max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False,
- widgets=widgets
+ with Progress(
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(bar_width=None),
+ MofNCompleteColumn(),
+ TaskProgressColumn(),
+ "•",
+ TimeElapsedColumn(),
+ "•",
+ TimeRemainingColumn(),
+ expand=True,
) as pbar:
+ task = pbar.add_task("Epochs", total=self.total_epochs)
+
start = 0
if self.analyze_per_epoch:
@@ -616,7 +599,7 @@ class Hyperopt:
f_val0 = self.generate_optimizer(asked[0])
self.opt.tell(asked, [f_val0['loss']])
self.evaluate_result(f_val0, 1, is_random[0])
- pbar.update(1)
+ pbar.update(task, advance=1)
start += 1
evals = ceil((self.total_epochs - start) / jobs)
@@ -630,14 +613,12 @@ class Hyperopt:
f_val = self.run_optimizer_parallel(parallel, asked)
self.opt.tell(asked, [v['loss'] for v in f_val])
- # Calculate progressbar outputs
for j, val in enumerate(f_val):
# Use human-friendly indexes here (starting from 1)
current = i * jobs + j + 1 + start
self.evaluate_result(val, current, is_random[j])
-
- pbar.update(current)
+ pbar.update(task, advance=1)
except KeyboardInterrupt:
print('User interrupted..')
diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py
index e2133a956..1e7befdf6 100644
--- a/freqtrade/optimize/hyperopt_tools.py
+++ b/freqtrade/optimize/hyperopt_tools.py
@@ -23,6 +23,8 @@ logger = logging.getLogger(__name__)
NON_OPT_PARAM_APPENDIX = " # value loaded from strategy"
+HYPER_PARAMS_FILE_FORMAT = rapidjson.NM_NATIVE | rapidjson.NM_NAN
+
def hyperopt_serializer(x):
if isinstance(x, np.integer):
@@ -76,9 +78,18 @@ class HyperoptTools():
with filename.open('w') as f:
rapidjson.dump(final_params, f, indent=2,
default=hyperopt_serializer,
- number_mode=rapidjson.NM_NATIVE | rapidjson.NM_NAN
+ number_mode=HYPER_PARAMS_FILE_FORMAT
)
+ @staticmethod
+ def load_params(filename: Path) -> Dict:
+ """
+ Load parameters from file
+ """
+ with filename.open('r') as f:
+ params = rapidjson.load(f, number_mode=HYPER_PARAMS_FILE_FORMAT)
+ return params
+
@staticmethod
def try_export_params(config: Config, strategy_name: str, params: Dict):
if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get('disableparamexport', False):
@@ -189,7 +200,7 @@ class HyperoptTools():
for s in ['buy', 'sell', 'protection',
'roi', 'stoploss', 'trailing', 'max_open_trades']:
HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s)
- print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE))
+ print(rapidjson.dumps(result_dict, default=str, number_mode=HYPER_PARAMS_FILE_FORMAT))
else:
HyperoptTools._params_pretty_print(params, 'buy', "Buy hyperspace params:",
diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py
index 83f698fbe..b4925770d 100644
--- a/freqtrade/optimize/optimize_reports.py
+++ b/freqtrade/optimize/optimize_reports.py
@@ -865,6 +865,11 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
print(table)
+ table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
+ if isinstance(table, str) and len(table) > 0:
+ print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
+ print(table)
+
if (results.get('results_per_enter_tag') is not None
or results.get('results_per_buy_tag') is not None):
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
@@ -884,11 +889,6 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
print(table)
- table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
- if isinstance(table, str) and len(table) > 0:
- print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
- print(table)
-
for period in backtest_breakdown:
days_breakdown_stats = generate_periodic_breakdown_stats(
trade_list=results['trades'], period=period)
@@ -917,11 +917,11 @@ def show_backtest_results(config: Config, backtest_stats: Dict):
strategy, results, stake_currency,
config.get('backtest_breakdown', []))
- if len(backtest_stats['strategy']) > 1:
+ if len(backtest_stats['strategy']) > 0:
# Print Strategy summary table
table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
- print(f"{results['backtest_start']} -> {results['backtest_end']} |"
+ print(f"Backtested {results['backtest_start']} -> {results['backtest_end']} |"
f" Max open trades : {results['max_open_trades']}")
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
print(table)
diff --git a/freqtrade/persistence/__init__.py b/freqtrade/persistence/__init__.py
index 9e1a7e922..4cf7aa455 100644
--- a/freqtrade/persistence/__init__.py
+++ b/freqtrade/persistence/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa: F401
+from freqtrade.persistence.key_value_store import KeyStoreKeys, KeyValueStore
from freqtrade.persistence.models import init_db
from freqtrade.persistence.pairlock_middleware import PairLocks
from freqtrade.persistence.trade_model import LocalTrade, Order, Trade
diff --git a/freqtrade/persistence/key_value_store.py b/freqtrade/persistence/key_value_store.py
new file mode 100644
index 000000000..2d26acbd3
--- /dev/null
+++ b/freqtrade/persistence/key_value_store.py
@@ -0,0 +1,179 @@
+from datetime import datetime, timezone
+from enum import Enum
+from typing import ClassVar, Optional, Union
+
+from sqlalchemy import String
+from sqlalchemy.orm import Mapped, mapped_column
+
+from freqtrade.persistence.base import ModelBase, SessionType
+
+
+ValueTypes = Union[str, datetime, float, int]
+
+
+class ValueTypesEnum(str, Enum):
+ STRING = 'str'
+ DATETIME = 'datetime'
+ FLOAT = 'float'
+ INT = 'int'
+
+
+class KeyStoreKeys(str, Enum):
+ BOT_START_TIME = 'bot_start_time'
+ STARTUP_TIME = 'startup_time'
+
+
+class _KeyValueStoreModel(ModelBase):
+ """
+ Pair Locks database model.
+ """
+ __tablename__ = 'KeyValueStore'
+ session: ClassVar[SessionType]
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ key: Mapped[KeyStoreKeys] = mapped_column(String(25), nullable=False, index=True)
+
+ value_type: Mapped[ValueTypesEnum] = mapped_column(String(20), nullable=False)
+
+ string_value: Mapped[Optional[str]]
+ datetime_value: Mapped[Optional[datetime]]
+ float_value: Mapped[Optional[float]]
+ int_value: Mapped[Optional[int]]
+
+
+class KeyValueStore():
+ """
+ Generic bot-wide, persistent key-value store
+ Can be used to store generic values, e.g. very first bot startup time.
+ Supports the types str, datetime, float and int.
+ """
+
+ @staticmethod
+ def store_value(key: KeyStoreKeys, value: ValueTypes) -> None:
+ """
+ Store the given value for the given key.
+ :param key: Key to store the value for - can be used in get-value to retrieve the key
+ :param value: Value to store - can be str, datetime, float or int
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key).first()
+ if kv is None:
+ kv = _KeyValueStoreModel(key=key)
+ if isinstance(value, str):
+ kv.value_type = ValueTypesEnum.STRING
+ kv.string_value = value
+ elif isinstance(value, datetime):
+ kv.value_type = ValueTypesEnum.DATETIME
+ kv.datetime_value = value
+ elif isinstance(value, float):
+ kv.value_type = ValueTypesEnum.FLOAT
+ kv.float_value = value
+ elif isinstance(value, int):
+ kv.value_type = ValueTypesEnum.INT
+ kv.int_value = value
+ else:
+ raise ValueError(f'Unknown value type {kv.value_type}')
+ _KeyValueStoreModel.session.add(kv)
+ _KeyValueStoreModel.session.commit()
+
+ @staticmethod
+ def delete_value(key: KeyStoreKeys) -> None:
+ """
+ Delete the value for the given key.
+ :param key: Key to delete the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key).first()
+ if kv is not None:
+ _KeyValueStoreModel.session.delete(kv)
+ _KeyValueStoreModel.session.commit()
+
+ @staticmethod
+ def get_value(key: KeyStoreKeys) -> Optional[ValueTypes]:
+ """
+ Get the value for the given key.
+ :param key: Key to get the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key).first()
+ if kv is None:
+ return None
+ if kv.value_type == ValueTypesEnum.STRING:
+ return kv.string_value
+ if kv.value_type == ValueTypesEnum.DATETIME and kv.datetime_value is not None:
+ return kv.datetime_value.replace(tzinfo=timezone.utc)
+ if kv.value_type == ValueTypesEnum.FLOAT:
+ return kv.float_value
+ if kv.value_type == ValueTypesEnum.INT:
+ return kv.int_value
+ # This should never happen unless someone messed with the database manually
+ raise ValueError(f'Unknown value type {kv.value_type}') # pragma: no cover
+
+ @staticmethod
+ def get_string_value(key: KeyStoreKeys) -> Optional[str]:
+ """
+ Get the value for the given key.
+ :param key: Key to get the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key,
+ _KeyValueStoreModel.value_type == ValueTypesEnum.STRING).first()
+ if kv is None:
+ return None
+ return kv.string_value
+
+ @staticmethod
+ def get_datetime_value(key: KeyStoreKeys) -> Optional[datetime]:
+ """
+ Get the value for the given key.
+ :param key: Key to get the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key,
+ _KeyValueStoreModel.value_type == ValueTypesEnum.DATETIME).first()
+ if kv is None or kv.datetime_value is None:
+ return None
+ return kv.datetime_value.replace(tzinfo=timezone.utc)
+
+ @staticmethod
+ def get_float_value(key: KeyStoreKeys) -> Optional[float]:
+ """
+ Get the value for the given key.
+ :param key: Key to get the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key,
+ _KeyValueStoreModel.value_type == ValueTypesEnum.FLOAT).first()
+ if kv is None:
+ return None
+ return kv.float_value
+
+ @staticmethod
+ def get_int_value(key: KeyStoreKeys) -> Optional[int]:
+ """
+ Get the value for the given key.
+ :param key: Key to get the value for
+ """
+ kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
+ _KeyValueStoreModel.key == key,
+ _KeyValueStoreModel.value_type == ValueTypesEnum.INT).first()
+ if kv is None:
+ return None
+ return kv.int_value
+
+
+def set_startup_time():
+ """
+ sets bot_start_time to the first trade open date - or "now" on new databases.
+ sets startup_time to "now"
+ """
+ st = KeyValueStore.get_value('bot_start_time')
+ if st is None:
+ from freqtrade.persistence import Trade
+ t = Trade.session.query(Trade).order_by(Trade.open_date.asc()).first()
+ if t is not None:
+ KeyValueStore.store_value('bot_start_time', t.open_date_utc)
+ else:
+ KeyValueStore.store_value('bot_start_time', datetime.now(timezone.utc))
+ KeyValueStore.store_value('startup_time', datetime.now(timezone.utc))
diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py
index 2315c0acc..e561e727b 100644
--- a/freqtrade/persistence/models.py
+++ b/freqtrade/persistence/models.py
@@ -13,6 +13,7 @@ from sqlalchemy.pool import StaticPool
from freqtrade.exceptions import OperationalException
from freqtrade.persistence.base import ModelBase
+from freqtrade.persistence.key_value_store import _KeyValueStoreModel
from freqtrade.persistence.migrations import check_migrate
from freqtrade.persistence.pairlock import PairLock
from freqtrade.persistence.trade_model import Order, Trade
@@ -76,6 +77,7 @@ def init_db(db_url: str) -> None:
bind=engine, autoflush=False), scopefunc=get_request_or_thread_id)
Order.session = Trade.session
PairLock.session = Trade.session
+ _KeyValueStoreModel.session = Trade.session
previous_tables = inspect(engine).get_table_names()
ModelBase.metadata.create_all(engine)
diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py
index 17117d436..0572b45a6 100644
--- a/freqtrade/persistence/trade_model.py
+++ b/freqtrade/persistence/trade_model.py
@@ -9,13 +9,14 @@ from typing import Any, ClassVar, Dict, List, Optional, Sequence, cast
from sqlalchemy import (Enum, Float, ForeignKey, Integer, ScalarResult, Select, String,
UniqueConstraint, desc, func, select)
-from sqlalchemy.orm import Mapped, lazyload, mapped_column, relationship
+from sqlalchemy.orm import Mapped, lazyload, mapped_column, relationship, validates
-from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES,
- BuySell, LongShort)
+from freqtrade.constants import (CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC,
+ NON_OPEN_EXCHANGE_STATES, BuySell, LongShort)
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.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
+ price_to_precision)
from freqtrade.leverage import interest
from freqtrade.persistence.base import ModelBase, SessionType
from freqtrade.util import FtPrecise
@@ -597,7 +598,8 @@ class LocalTrade():
"""
Method used internally to set self.stop_loss.
"""
- stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode)
+ stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode,
+ rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP)
if not self.stop_loss:
self.initial_stop_loss = stop_loss_norm
self.stop_loss = stop_loss_norm
@@ -628,7 +630,8 @@ class LocalTrade():
if self.initial_stop_loss_pct is None or refresh:
self.__set_stop_loss(new_loss, stoploss)
self.initial_stop_loss = price_to_precision(
- new_loss, self.price_precision, self.precision_mode)
+ new_loss, self.price_precision, self.precision_mode,
+ rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP)
self.initial_stop_loss_pct = -1 * abs(stoploss)
# evaluate if the stop loss needs to be updated
@@ -692,21 +695,24 @@ class LocalTrade():
else:
logger.warning(
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
+
+ elif order.ft_order_side == 'stoploss' and order.status not in ('open', ):
+ self.stoploss_order_id = None
+ self.close_rate_requested = self.stop_loss
+ self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
+ if self.is_open:
+ logger.info(f'{order.order_type.upper()} is hit for {self}.')
+ else:
+ raise ValueError(f'Unknown order type: {order.order_type}')
+
+ if order.ft_order_side != self.entry_side:
amount_tr = amount_to_contract_precision(self.amount, self.amount_precision,
self.precision_mode, self.contract_size)
if isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC):
self.close(order.safe_price)
else:
self.recalc_trade_from_orders()
- elif order.ft_order_side == 'stoploss' and order.status not in ('canceled', 'open'):
- self.stoploss_order_id = None
- self.close_rate_requested = self.stop_loss
- self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
- if self.is_open:
- logger.info(f'{order.order_type.upper()} is hit for {self}.')
- self.close(order.safe_price)
- else:
- raise ValueError(f'Unknown order type: {order.order_type}')
+
Trade.commit()
def close(self, rate: float, *, show_msg: bool = True) -> None:
@@ -1253,11 +1259,13 @@ class Trade(ModelBase, LocalTrade):
Float(), nullable=True, default=0.0) # type: ignore
# Lowest price reached
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_reason: Mapped[Optional[str]] = mapped_column(
+ String(CUSTOM_TAG_MAX_LENGTH), 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
+ enter_tag: Mapped[Optional[str]] = mapped_column(
+ String(CUSTOM_TAG_MAX_LENGTH), nullable=True) # type: ignore
timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
trading_mode: Mapped[TradingMode] = mapped_column(
@@ -1287,6 +1295,13 @@ class Trade(ModelBase, LocalTrade):
self.realized_profit = 0
self.recalc_open_trade_value()
+ @validates('enter_tag', 'exit_reason')
+ def validate_string_len(self, key, value):
+ max_len = getattr(self.__class__, key).prop.columns[0].type.length
+ if value and len(value) > max_len:
+ return value[:max_len]
+ return value
+
def delete(self) -> None:
for order in self.orders:
diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py
index 478eaec20..2e74aa293 100644
--- a/freqtrade/plugins/pairlist/PrecisionFilter.py
+++ b/freqtrade/plugins/pairlist/PrecisionFilter.py
@@ -6,6 +6,7 @@ from typing import Any, Dict, Optional
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
+from freqtrade.exchange import ROUND_UP
from freqtrade.exchange.types import Ticker
from freqtrade.plugins.pairlist.IPairList import IPairList
@@ -61,9 +62,10 @@ class PrecisionFilter(IPairList):
stop_price = ticker['last'] * self._stoploss
# Adjust stop-prices to precision
- sp = self._exchange.price_to_precision(pair, stop_price)
+ sp = self._exchange.price_to_precision(pair, stop_price, rounding_mode=ROUND_UP)
- stop_gap_price = self._exchange.price_to_precision(pair, stop_price * 0.99)
+ stop_gap_price = self._exchange.price_to_precision(pair, stop_price * 0.99,
+ rounding_mode=ROUND_UP)
logger.debug(f"{pair} - {sp} : {stop_gap_price}")
if sp <= stop_gap_price:
diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py
index 764c16f1a..d077330e0 100644
--- a/freqtrade/plugins/pairlist/RemotePairList.py
+++ b/freqtrade/plugins/pairlist/RemotePairList.py
@@ -143,6 +143,9 @@ class RemotePairList(IPairList):
if self._init_done:
pairlist = self._pair_cache.get('pairlist')
+ if pairlist == [None]:
+ # Valid but empty pairlist.
+ return []
else:
pairlist = []
@@ -181,7 +184,11 @@ class RemotePairList(IPairList):
pairlist = self._whitelist_for_active_markets(pairlist)
pairlist = pairlist[:self._number_pairs]
- self._pair_cache['pairlist'] = pairlist.copy()
+ if pairlist:
+ self._pair_cache['pairlist'] = pairlist.copy()
+ else:
+ # If pairlist is empty, set a dummy value to avoid fetching again
+ self._pair_cache['pairlist'] = [None]
if time_elapsed != 0.0:
self.log_once(f'Pairlist Fetched in {time_elapsed} seconds.', logger.info)
diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py
index 7497b27f1..53bf7558f 100644
--- a/freqtrade/rpc/api_server/api_schemas.py
+++ b/freqtrade/rpc/api_server/api_schemas.py
@@ -108,6 +108,8 @@ class Profit(BaseModel):
max_drawdown: float
max_drawdown_abs: float
trading_volume: Optional[float]
+ bot_start_timestamp: int
+ bot_start_date: str
class SellReason(BaseModel):
diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py
index a79c1a5fc..48786bec2 100644
--- a/freqtrade/rpc/api_server/uvicorn_threaded.py
+++ b/freqtrade/rpc/api_server/uvicorn_threaded.py
@@ -55,7 +55,7 @@ class UvicornServer(uvicorn.Server):
@contextlib.contextmanager
def run_in_thread(self):
- self.thread = threading.Thread(target=self.run)
+ self.thread = threading.Thread(target=self.run, name='FTUvicorn')
self.thread.start()
while not self.started:
time.sleep(1e-3)
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 2b5eb107c..ffed3c6d6 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -26,7 +26,7 @@ from freqtrade.exceptions import ExchangeError, PricingError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
from freqtrade.loggers import bufferHandler
from freqtrade.misc import decimals_per_coin, shorten_date
-from freqtrade.persistence import Order, PairLocks, Trade
+from freqtrade.persistence import KeyStoreKeys, KeyValueStore, 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
@@ -543,6 +543,7 @@ class RPC:
first_date = trades[0].open_date if trades else None
last_date = trades[-1].open_date if trades else None
num = float(len(durations) or 1)
+ bot_start = KeyValueStore.get_datetime_value(KeyStoreKeys.BOT_START_TIME)
return {
'profit_closed_coin': profit_closed_coin_sum,
'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2),
@@ -576,6 +577,8 @@ class RPC:
'max_drawdown': max_drawdown,
'max_drawdown_abs': max_drawdown_abs,
'trading_volume': trading_volume,
+ 'bot_start_timestamp': int(bot_start.timestamp() * 1000) if bot_start else 0,
+ 'bot_start_date': bot_start.strftime(DATETIME_PRINT_FORMAT) if bot_start else '',
}
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
@@ -1193,6 +1196,7 @@ class RPC:
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategy = StrategyResolver.load_strategy(config)
strategy.dp = DataProvider(config, exchange=exchange, pairlists=None)
+ strategy.ft_bot_start()
df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair})
diff --git a/freqtrade/rpc/rpc_types.py b/freqtrade/rpc/rpc_types.py
index 3277a2d6e..23f3ed5a9 100644
--- a/freqtrade/rpc/rpc_types.py
+++ b/freqtrade/rpc/rpc_types.py
@@ -52,7 +52,7 @@ class __RPCBuyMsgBase(RPCSendMsgBase):
direction: str
limit: float
open_rate: float
- order_type: Optional[str] # TODO: why optional??
+ order_type: str
stake_amount: float
stake_currency: str
fiat_currency: Optional[str]
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index d79d8ea76..51f7fb9ee 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -66,10 +66,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
chat_id = int(self._config['telegram']['chat_id'])
if cchat_id != chat_id:
- logger.info(
- 'Rejected unauthorized message from: %s',
- update.message.chat_id
- )
+ logger.info(f'Rejected unauthorized message from: {update.message.chat_id}')
return wrapper
# Rollback session to avoid getting data stored in a transaction.
Trade.rollback()
@@ -819,7 +816,7 @@ class Telegram(RPCHandler):
best_pair = stats['best_pair']
best_pair_profit_ratio = stats['best_pair_profit_ratio']
if stats['trade_count'] == 0:
- markdown_msg = 'No trades yet.'
+ markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`"
else:
# Message to display
if stats['closed_trade_count'] > 0:
@@ -838,6 +835,7 @@ class Telegram(RPCHandler):
f"({profit_all_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
f"*Total Trade Count:* `{trade_count}`\n"
+ f"*Bot started:* `{stats['bot_start_date']}`\n"
f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
f"`{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_trade_date}`\n"
diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py
index 52ba22951..d38110a2a 100644
--- a/freqtrade/strategy/hyper.py
+++ b/freqtrade/strategy/hyper.py
@@ -8,7 +8,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
-from freqtrade.misc import deep_merge_dicts, json_load
+from freqtrade.misc import deep_merge_dicts
from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.strategy.parameters import BaseParameter
@@ -124,8 +124,7 @@ class HyperStrategyMixin:
if filename.is_file():
logger.info(f"Loading parameters from file {filename}")
try:
- with filename.open('r') as f:
- params = json_load(f)
+ params = HyperoptTools.load_params(filename)
if params.get('strategy_name') != self.__class__.__name__:
raise OperationalException('Invalid parameter file provided.')
return params
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 6d4a3036f..3bc766d91 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -10,7 +10,7 @@ from typing import Dict, List, Optional, Tuple, Union
import arrow
from pandas import DataFrame
-from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes
+from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, Config, IntOrInf, ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode,
SignalDirection, SignalTagType, SignalType, TradingMode)
@@ -27,7 +27,6 @@ from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__)
-CUSTOM_EXIT_MAX_LENGTH = 64
class IStrategy(ABC, HyperStrategyMixin):
@@ -1118,11 +1117,11 @@ class IStrategy(ABC, HyperStrategyMixin):
exit_signal = ExitType.CUSTOM_EXIT
if isinstance(reason_cust, str):
custom_reason = reason_cust
- if len(reason_cust) > CUSTOM_EXIT_MAX_LENGTH:
+ if len(reason_cust) > CUSTOM_TAG_MAX_LENGTH:
logger.warning(f'Custom exit reason returned from '
f'custom_exit is too long and was trimmed'
- f'to {CUSTOM_EXIT_MAX_LENGTH} characters.')
- custom_reason = reason_cust[:CUSTOM_EXIT_MAX_LENGTH]
+ f'to {CUSTOM_TAG_MAX_LENGTH} characters.')
+ custom_reason = reason_cust[:CUSTOM_TAG_MAX_LENGTH]
else:
custom_reason = ''
if (
diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py
index 0e7113f8c..3f27ee4a1 100644
--- a/freqtrade/templates/FreqaiExampleHybridStrategy.py
+++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py
@@ -223,6 +223,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
:param metadata: metadata of current pair
usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"]
"""
+ self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-50) >
dataframe["close"], 'up', 'down')
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 3324c11e9..690a7ee70 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,10 +7,10 @@
-r docs/requirements-docs.txt
coveralls==3.3.1
-ruff==0.0.259
-mypy==1.1.1
-pre-commit==3.2.1
-pytest==7.2.2
+ruff==0.0.261
+mypy==1.2.0
+pre-commit==3.2.2
+pytest==7.3.0
pytest-asyncio==0.21.0
pytest-cov==4.0.0
pytest-mock==3.10.0
@@ -22,11 +22,11 @@ time-machine==2.9.0
httpx==0.23.3
# Convert jupyter notebooks to markdown documents
-nbconvert==7.2.10
+nbconvert==7.3.1
# mypy types
-types-cachetools==5.3.0.4
+types-cachetools==5.3.0.5
types-filelock==3.2.7
-types-requests==2.28.11.16
-types-tabulate==0.9.0.1
-types-python-dateutil==2.8.19.10
+types-requests==2.28.11.17
+types-tabulate==0.9.0.2
+types-python-dateutil==2.8.19.12
diff --git a/requirements-freqai.txt b/requirements-freqai.txt
index e6eae667c..840598d23 100644
--- a/requirements-freqai.txt
+++ b/requirements-freqai.txt
@@ -7,5 +7,5 @@ scikit-learn==1.1.3
joblib==1.2.0
catboost==1.1.1; platform_machine != 'aarch64' and 'arm' not in platform_machine and python_version < '3.11'
lightgbm==3.3.5
-xgboost==1.7.4
-tensorboard==2.12.0
+xgboost==1.7.5
+tensorboard==2.12.1
diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt
index 2c7c27d98..c81c17f63 100644
--- a/requirements-hyperopt.txt
+++ b/requirements-hyperopt.txt
@@ -5,5 +5,4 @@
scipy==1.10.1
scikit-learn==1.1.3
scikit-optimize==0.9.0
-filelock==3.10.6
-progressbar2==4.2.0
+filelock==3.11.0
diff --git a/requirements-plot.txt b/requirements-plot.txt
index ad7bade95..8b9ad5bc4 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.1
+plotly==5.14.1
diff --git a/requirements.txt b/requirements.txt
index b888d9f6e..e09f8ba77 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,17 +2,17 @@ numpy==1.24.2
pandas==1.5.3
pandas-ta==0.3.14b
-ccxt==3.0.37
+ccxt==3.0.59
cryptography==40.0.1
aiohttp==3.8.4
-SQLAlchemy==2.0.7
+SQLAlchemy==2.0.9
python-telegram-bot==13.15
arrow==1.2.3
cachetools==4.2.2
requests==2.28.2
urllib3==1.26.15
jsonschema==4.17.3
-TA-Lib==0.4.25
+TA-Lib==0.4.26
technical==1.4.0
tabulate==0.9.0
pycoingecko==3.1.0
@@ -20,6 +20,7 @@ jinja2==3.1.2
tables==3.8.0
blosc==1.11.1
joblib==1.2.0
+rich==13.3.3
pyarrow==11.0.0; platform_machine != 'armv7l'
# find first, C search in arrays
@@ -28,7 +29,7 @@ py_find_1st==1.1.5
# Load ticker files 30% faster
python-rapidjson==1.10
# Properly format api responses
-orjson==3.8.8
+orjson==3.8.10
# Notify systemd
sdnotify==0.3.2
@@ -50,10 +51,10 @@ prompt-toolkit==3.0.38
python-dateutil==2.8.2
#Futures
-schedule==1.1.0
+schedule==1.2.0
#WS Messages
-websockets==10.4
+websockets==11.0.1
janus==1.0.0
ast-comments==1.0.1
diff --git a/setup.py b/setup.py
index edd7b243b..048dc066d 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,6 @@ hyperopt = [
'scikit-learn',
'scikit-optimize>=0.7.0',
'filelock',
- 'progressbar2',
]
freqai = [
@@ -59,7 +58,7 @@ setup(
install_requires=[
# from requirements.txt
'ccxt>=2.6.26',
- 'SQLAlchemy',
+ 'SQLAlchemy>=2.0.6',
'python-telegram-bot>=13.4',
'arrow>=0.17.0',
'cachetools',
@@ -82,6 +81,7 @@ setup(
'numpy',
'pandas',
'joblib>=1.2.0',
+ 'rich',
'pyarrow; platform_machine != "armv7l"',
'fastapi',
'pydantic>=1.8.0',
diff --git a/setup.sh b/setup.sh
index a9ff36536..805e13237 100755
--- a/setup.sh
+++ b/setup.sh
@@ -50,7 +50,7 @@ function updateenv() {
SYS_ARCH=$(uname -m)
echo "pip install in-progress. Please wait..."
# Setuptools 65.5.0 is the last version that can install gym==0.21.0
- ${PYTHON} -m pip install --upgrade pip wheel setuptools==65.5.1
+ ${PYTHON} -m pip install --upgrade pip==23.0.1 wheel setuptools==65.5.1
REQUIREMENTS_HYPEROPT=""
REQUIREMENTS_PLOT=""
REQUIREMENTS_FREQAI=""
@@ -85,7 +85,7 @@ function updateenv() {
if [[ $REPLY =~ ^[Yy]$ ]]
then
REQUIREMENTS_FREQAI="-r requirements-freqai.txt --use-pep517"
- read -p "Do you also want dependencies for freqai-rl (~700mb additional space required) [y/N]? "
+ read -p "Do you also want dependencies for freqai-rl or PyTorch (~700mb additional space required) [y/N]? "
if [[ $REPLY =~ ^[Yy]$ ]]
then
REQUIREMENTS_FREQAI="-r requirements-freqai-rl.txt"
diff --git a/tests/conftest.py b/tests/conftest.py
index 0aa6e70a8..2f2345d54 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -504,7 +504,7 @@ def get_default_conf(testdatadir):
{"method": "StaticPairList"}
],
"telegram": {
- "enabled": True,
+ "enabled": False,
"token": "token",
"chat_id": "0",
"notification_settings": {},
diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py
index fda33b859..d44dae00d 100644
--- a/tests/exchange/test_binance.py
+++ b/tests/exchange/test_binance.py
@@ -48,7 +48,7 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte
default_conf['margin_mode'] = MarginMode.ISOLATED
default_conf['trading_mode'] = trademode
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
- mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
@@ -127,7 +127,7 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
order_type = 'stop_loss_limit'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
- mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 5350f4e3e..b0760944a 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
import arrow
import ccxt
import pytest
+from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE
from pandas import DataFrame
from freqtrade.enums import CandleType, MarginMode, TradingMode
@@ -315,35 +316,54 @@ def test_amount_to_precision(amount, precision_mode, precision, expected,):
assert amount_to_precision(amount, precision, precision_mode) == expected
-@pytest.mark.parametrize("price,precision_mode,precision,expected", [
- (2.34559, 2, 4, 2.3456),
- (2.34559, 2, 5, 2.34559),
- (2.34559, 2, 3, 2.346),
- (2.9999, 2, 3, 3.000),
- (2.9909, 2, 3, 2.991),
- # Tests for Tick_size
- (2.34559, 4, 0.0001, 2.3456),
- (2.34559, 4, 0.00001, 2.34559),
- (2.34559, 4, 0.001, 2.346),
- (2.9999, 4, 0.001, 3.000),
- (2.9909, 4, 0.001, 2.991),
- (2.9909, 4, 0.005, 2.995),
- (2.9973, 4, 0.005, 3.0),
- (2.9977, 4, 0.005, 3.0),
- (234.43, 4, 0.5, 234.5),
- (234.53, 4, 0.5, 235.0),
- (0.891534, 4, 0.0001, 0.8916),
- (64968.89, 4, 0.01, 64968.89),
- (0.000000003483, 4, 1e-12, 0.000000003483),
-
+@pytest.mark.parametrize("price,precision_mode,precision,expected,rounding_mode", [
+ # Tests for DECIMAL_PLACES, ROUND_UP
+ (2.34559, 2, 4, 2.3456, ROUND_UP),
+ (2.34559, 2, 5, 2.34559, ROUND_UP),
+ (2.34559, 2, 3, 2.346, ROUND_UP),
+ (2.9999, 2, 3, 3.000, ROUND_UP),
+ (2.9909, 2, 3, 2.991, ROUND_UP),
+ # Tests for DECIMAL_PLACES, ROUND
+ (2.345600000000001, DECIMAL_PLACES, 4, 2.3456, ROUND),
+ (2.345551, DECIMAL_PLACES, 4, 2.3456, ROUND),
+ (2.49, DECIMAL_PLACES, 0, 2., ROUND),
+ (2.51, DECIMAL_PLACES, 0, 3., ROUND),
+ (5.1, DECIMAL_PLACES, -1, 10., ROUND),
+ (4.9, DECIMAL_PLACES, -1, 0., ROUND),
+ # Tests for TICK_SIZE, ROUND_UP
+ (2.34559, TICK_SIZE, 0.0001, 2.3456, ROUND_UP),
+ (2.34559, TICK_SIZE, 0.00001, 2.34559, ROUND_UP),
+ (2.34559, TICK_SIZE, 0.001, 2.346, ROUND_UP),
+ (2.9999, TICK_SIZE, 0.001, 3.000, ROUND_UP),
+ (2.9909, TICK_SIZE, 0.001, 2.991, ROUND_UP),
+ (2.9909, TICK_SIZE, 0.005, 2.995, ROUND_UP),
+ (2.9973, TICK_SIZE, 0.005, 3.0, ROUND_UP),
+ (2.9977, TICK_SIZE, 0.005, 3.0, ROUND_UP),
+ (234.43, TICK_SIZE, 0.5, 234.5, ROUND_UP),
+ (234.53, TICK_SIZE, 0.5, 235.0, ROUND_UP),
+ (0.891534, TICK_SIZE, 0.0001, 0.8916, ROUND_UP),
+ (64968.89, TICK_SIZE, 0.01, 64968.89, ROUND_UP),
+ (0.000000003483, TICK_SIZE, 1e-12, 0.000000003483, ROUND_UP),
+ # Tests for TICK_SIZE, ROUND
+ (2.49, TICK_SIZE, 1., 2., ROUND),
+ (2.51, TICK_SIZE, 1., 3., ROUND),
+ (2.000000051, TICK_SIZE, 0.0000001, 2.0000001, ROUND),
+ (2.000000049, TICK_SIZE, 0.0000001, 2., ROUND),
+ (2.9909, TICK_SIZE, 0.005, 2.990, ROUND),
+ (2.9973, TICK_SIZE, 0.005, 2.995, ROUND),
+ (2.9977, TICK_SIZE, 0.005, 3.0, ROUND),
+ (234.24, TICK_SIZE, 0.5, 234., ROUND),
+ (234.26, TICK_SIZE, 0.5, 234.5, ROUND),
+ # Tests for TRUNCATTE
+ (2.34559, 2, 4, 2.3455, TRUNCATE),
+ (2.34559, 2, 5, 2.34559, TRUNCATE),
+ (2.34559, 2, 3, 2.345, TRUNCATE),
+ (2.9999, 2, 3, 2.999, TRUNCATE),
+ (2.9909, 2, 3, 2.990, TRUNCATE),
])
-def test_price_to_precision(price, precision_mode, precision, expected):
- # digits counting mode
- # DECIMAL_PLACES = 2
- # SIGNIFICANT_DIGITS = 3
- # TICK_SIZE = 4
-
- assert price_to_precision(price, precision, precision_mode) == expected
+def test_price_to_precision(price, precision_mode, precision, expected, rounding_mode):
+ assert price_to_precision(
+ price, precision, precision_mode, rounding_mode=rounding_mode) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
@@ -417,7 +437,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
}
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))
+ expected_result = 2 * 2 * (1 + 0.05)
assert pytest.approx(result) == expected_result
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
@@ -426,14 +446,14 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
assert result == 20000
- # min amount and cost are set (cost is minimal)
+ # min amount and cost are set (cost is minimal and therefore ignored)
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 2, 'max': None},
'amount': {'min': 2, 'max': None},
}
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))
+ expected_result = max(2, 2 * 2) * (1 + 0.05)
assert pytest.approx(result) == expected_result
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
@@ -476,6 +496,9 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
assert result == 1000
+ result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, 12.0)
+ assert result == 1000 / 12
+
markets["ETH/BTC"]["contractSize"] = '0.01'
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
@@ -1232,9 +1255,10 @@ def test_create_dry_run_order_fees(
("buy", 29.563, True, True),
("sell", 21.563, True, True),
])
+@pytest.mark.parametrize("leverage", [1, 2, 5])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog,
- exchange_name, order_book_l2_usd, converted):
+ exchange_name, order_book_l2_usd, converted, leverage):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple(EXMS,
@@ -1248,7 +1272,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
side=side,
amount=1,
rate=price,
- leverage=1.0
+ leverage=leverage,
)
assert order_book_l2_usd.call_count == 1
assert 'id' in order
@@ -1272,6 +1296,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
assert order_book_l2_usd.call_count == (1 if not filled else 0)
assert order_closed['status'] == ('open' if not filled else 'closed')
assert order_closed['filled'] == (0 if not filled else 1)
+ assert order_closed['cost'] == 1 * order_closed['average']
order_book_l2_usd.reset_mock()
@@ -1294,9 +1319,10 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
("sell", 25.564, 1000, 25.5555), # More than orderbook return
("sell", 27, 10000, 25.65), # max-slippage 5%
])
+@pytest.mark.parametrize("leverage", [1, 2, 5])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice,
- exchange_name, order_book_l2_usd):
+ exchange_name, order_book_l2_usd, leverage):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple(EXMS,
@@ -1310,7 +1336,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
side=side,
amount=amount,
rate=rate,
- leverage=1.0
+ leverage=leverage,
)
assert 'id' in order
assert f'dry_run_{side}_' in order["id"]
@@ -1319,6 +1345,8 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
assert order["symbol"] == "LTC/USDT"
assert order['status'] == 'closed'
assert order['filled'] == amount
+ assert order['amount'] == amount
+ assert pytest.approx(order['cost']) == amount * order['average']
assert round(order["average"], 4) == round(endprice, 4)
@@ -1439,7 +1467,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'
@@ -1544,7 +1575,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]
@@ -1588,7 +1622,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'
@@ -1682,7 +1719,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]
@@ -5269,7 +5309,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
})
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)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_contract_size = MagicMock(return_value=contract_size)
@@ -5289,3 +5329,10 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
assert order['cost'] == 100
assert order['filled'] == 100
assert order['remaining'] == 100
+
+
+def test_price_to_precision_with_default_conf(default_conf, mocker):
+ conf = copy.deepcopy(default_conf)
+ patched_ex = get_patched_exchange(mocker, conf)
+ prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
+ assert prec_price == 1.00000001
diff --git a/tests/exchange/test_gate.py b/tests/exchange/test_gate.py
index db7591a40..3cb5a9a3e 100644
--- a/tests/exchange/test_gate.py
+++ b/tests/exchange/test_gate.py
@@ -4,42 +4,9 @@ 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 EXMS, get_patched_exchange
-def test_validate_order_types_gate(default_conf, mocker):
- default_conf['exchange']['name'] = 'gate'
- mocker.patch(f'{EXMS}._init_ccxt')
- mocker.patch(f'{EXMS}._load_markets', 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', '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
-
-
@pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_gate(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='gate')
diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py
index 85d2ced9d..8be8ef8b3 100644
--- a/tests/exchange/test_huobi.py
+++ b/tests/exchange/test_huobi.py
@@ -27,7 +27,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
})
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)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
@@ -80,7 +80,7 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
order_type = 'stop-limit'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
- mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py
index 40a5a5b38..8fc23b94e 100644
--- a/tests/exchange/test_kraken.py
+++ b/tests/exchange/test_kraken.py
@@ -29,7 +29,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
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)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(
@@ -192,7 +192,7 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
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)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
@@ -263,7 +263,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
- mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: 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 07f3fb6a3..741ee27be 100644
--- a/tests/exchange/test_kucoin.py
+++ b/tests/exchange/test_kucoin.py
@@ -27,7 +27,7 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected
})
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)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
if order_type == 'limit':
@@ -88,7 +88,7 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
order_type = 'market'
default_conf['dry_run'] = True
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
- mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
+ mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py
index e140ee80b..ab4a62a9e 100644
--- a/tests/freqai/conftest.py
+++ b/tests/freqai/conftest.py
@@ -1,5 +1,6 @@
from copy import deepcopy
from pathlib import Path
+from typing import Any, Dict
from unittest.mock import MagicMock
import pytest
@@ -85,6 +86,22 @@ def make_rl_config(conf):
return conf
+def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
+ return {
+ "learning_rate": 3e-4,
+ "trainer_kwargs": {
+ "max_iters": 1,
+ "batch_size": 64,
+ "max_n_eval_batches": 1,
+ },
+ "model_kwargs": {
+ "hidden_dim": 32,
+ "dropout_percent": 0.2,
+ "n_layer": 1,
+ }
+ }
+
+
def get_patched_data_kitchen(mocker, freqaiconf):
dk = FreqaiDataKitchen(freqaiconf)
return dk
@@ -119,6 +136,7 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
freqai.dk.pair = "ADA/BTC"
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
@@ -152,6 +170,7 @@ def make_data_dictionary(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
freqai.dk.pair = "ADA/BTC"
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
diff --git a/tests/freqai/test_freqai_datadrawer.py b/tests/freqai/test_freqai_datadrawer.py
index da3b8f9c1..8ab2c75da 100644
--- a/tests/freqai/test_freqai_datadrawer.py
+++ b/tests/freqai/test_freqai_datadrawer.py
@@ -19,6 +19,7 @@ def test_update_historic_data(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -41,6 +42,7 @@ def test_load_all_pairs_histories(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -60,6 +62,7 @@ def test_get_base_and_corr_dataframes(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
@@ -87,6 +90,7 @@ def test_use_strategy_to_populate_indicators(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180114")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
@@ -103,8 +107,9 @@ def test_get_timerange_from_live_historic_predictions(mocker, freqai_conf):
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
freqai = strategy.freqai
- freqai.live = True
+ freqai.live = False
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = False
timerange = TimeRange.parse_timerange("20180126-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
diff --git a/tests/freqai/test_freqai_datakitchen.py b/tests/freqai/test_freqai_datakitchen.py
index 95665a775..3f0fc697d 100644
--- a/tests/freqai/test_freqai_datakitchen.py
+++ b/tests/freqai/test_freqai_datakitchen.py
@@ -180,6 +180,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py
index 3b370aea4..7346191db 100644
--- a/tests/freqai/test_freqai_interface.py
+++ b/tests/freqai/test_freqai_interface.py
@@ -15,7 +15,8 @@ from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
-from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config
+from tests.freqai.conftest import (get_patched_freqai_strategy, make_rl_config,
+ mock_pytorch_mlp_model_training_parameters)
def is_py11() -> bool:
@@ -34,13 +35,14 @@ def is_mac() -> bool:
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")
+ 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")
+ is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
+ if is_pytorch_model and is_mac() and not is_arm():
+ pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
- if is_py11() and 'Reinforcement' in model:
- pytest.skip("Reinforcement learning currently not available on python 3.11.")
+ if is_pytorch_model and is_py11():
+ pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.")
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
@@ -48,11 +50,12 @@ def can_run_model(model: str) -> None:
('XGBoostRegressor', False, True, False, True, False, 10),
('XGBoostRFRegressor', False, False, False, True, False, 0),
('CatboostRegressor', False, False, False, True, True, 0),
+ ('PyTorchMLPRegressor', False, False, False, True, False, 0),
('ReinforcementLearner', False, True, False, True, False, 0),
('ReinforcementLearner_multiproc', False, False, False, True, False, 0),
('ReinforcementLearner_test_3ac', False, False, False, False, False, 0),
('ReinforcementLearner_test_3ac', False, False, False, True, False, 0),
- ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0)
+ ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0),
])
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
dbscan, float32, can_short, shuffle, buffer):
@@ -79,6 +82,11 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
+ if 'PyTorchMLPRegressor' in model:
+ model_save_ext = 'zip'
+ pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
+ freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
+
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf)
strategy.dp = DataProvider(freqai_conf, exchange)
@@ -87,6 +95,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai.live = True
freqai.can_short = can_short
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
freqai.dk.set_paths('ADA/BTC', 10000)
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -122,8 +131,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() or is_py11()) and 'Catboost' in model:
- pytest.skip("CatBoost is not supported on ARM")
+ can_run_model(model)
freqai_conf.update({"timerange": "20180110-20180130"})
freqai_conf.update({"strategy": strat})
@@ -135,6 +143,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -162,10 +171,10 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
'CatboostClassifier',
'XGBoostClassifier',
'XGBoostRFClassifier',
+ 'PyTorchMLPClassifier',
])
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
- if (is_arm() or is_py11()) and model == 'CatboostClassifier':
- pytest.skip("CatBoost is not supported on ARM")
+ can_run_model(model)
freqai_conf.update({"freqaimodel": model})
freqai_conf.update({"strategy": "freqai_test_classifier"})
@@ -178,6 +187,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -190,7 +200,20 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
strategy, freqai.dk, data_load_timerange)
- assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists()
+ if 'PyTorchMLPClassifier':
+ pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
+ freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
+
+ if freqai.dd.model_type == 'joblib':
+ model_file_extension = ".joblib"
+ elif freqai.dd.model_type == "pytorch":
+ model_file_extension = ".zip"
+ else:
+ raise Exception(f"Unsupported model type: {freqai.dd.model_type},"
+ f" can't assign model_file_extension")
+
+ assert Path(freqai.dk.data_path /
+ f"{freqai.dk.model_filename}_model{model_file_extension}").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists()
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists()
@@ -204,10 +227,12 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
("LightGBMRegressor", 2, "freqai_test_strat"),
("XGBoostRegressor", 2, "freqai_test_strat"),
("CatboostRegressor", 2, "freqai_test_strat"),
+ ("PyTorchMLPRegressor", 2, "freqai_test_strat"),
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
("XGBoostClassifier", 2, "freqai_test_classifier"),
("LightGBMClassifier", 2, "freqai_test_classifier"),
- ("CatboostClassifier", 2, "freqai_test_classifier")
+ ("CatboostClassifier", 2, "freqai_test_classifier"),
+ ("PyTorchMLPClassifier", 2, "freqai_test_classifier")
],
)
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
@@ -228,6 +253,10 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
if 'test_4ac' in model:
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
+ if 'PyTorchMLP' in model:
+ pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
+ freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
+
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"indicator_periods_candles": [2]})
@@ -371,6 +400,9 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
+ df = strategy.set_freqai_targets(df.copy(), metadata={"pair": "LTC/BTC"})
+ df = freqai.dk.remove_special_chars_from_feature_names(df)
+ freqai.dk.get_unique_classes_from_labels(df)
freqai.dk.pair = "ADA/BTC"
freqai.dk.full_df = df.fillna(0)
freqai.dk.full_df
@@ -394,6 +426,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
@@ -425,10 +458,12 @@ def test_plot_feature_importance(mocker, freqai_conf):
freqai = strategy.freqai
freqai.live = True
freqai.dk = FreqaiDataKitchen(freqai_conf)
+ freqai.dk.live = True
timerange = TimeRange.parse_timerange("20180110-20180130")
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
- freqai.dd.pair_dict = MagicMock()
+ freqai.dd.pair_dict = {"ADA/BTC": {"model_filename": "fake_name",
+ "trained_timestamp": 1, "data_path": "", "extras": {}}}
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
new_timerange = TimeRange.parse_timerange("20180120-20180130")
diff --git a/tests/persistence/test_key_value_store.py b/tests/persistence/test_key_value_store.py
new file mode 100644
index 000000000..1dab8764a
--- /dev/null
+++ b/tests/persistence/test_key_value_store.py
@@ -0,0 +1,69 @@
+from datetime import datetime, timedelta, timezone
+
+import pytest
+
+from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
+from tests.conftest import create_mock_trades_usdt
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_key_value_store(time_machine):
+ start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
+ time_machine.move_to(start, tick=False)
+
+ KeyValueStore.store_value("test", "testStringValue")
+ KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
+ KeyValueStore.store_value("test_float", 22.51)
+ KeyValueStore.store_value("test_int", 15)
+
+ assert KeyValueStore.get_value("test") == "testStringValue"
+ assert KeyValueStore.get_value("test") == "testStringValue"
+ assert KeyValueStore.get_string_value("test") == "testStringValue"
+ assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
+ assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
+ assert KeyValueStore.get_string_value("test_dt") is None
+ assert KeyValueStore.get_float_value("test_dt") is None
+ assert KeyValueStore.get_int_value("test_dt") is None
+ assert KeyValueStore.get_value("test_float") == 22.51
+ assert KeyValueStore.get_float_value("test_float") == 22.51
+ assert KeyValueStore.get_value("test_int") == 15
+ assert KeyValueStore.get_int_value("test_int") == 15
+ assert KeyValueStore.get_datetime_value("test_int") is None
+
+ time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
+ assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
+ assert KeyValueStore.get_value("test_dt") == start
+ # Test update works
+ KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
+ assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
+
+ KeyValueStore.store_value("test_float", 23.51)
+ assert KeyValueStore.get_value("test_float") == 23.51
+ # test deleting
+ KeyValueStore.delete_value("test_float")
+ assert KeyValueStore.get_value("test_float") is None
+ # Delete same value again (should not fail)
+ KeyValueStore.delete_value("test_float")
+
+ with pytest.raises(ValueError, match=r"Unknown value type"):
+ KeyValueStore.store_value("test_float", {'some': 'dict'})
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_set_startup_time(fee, time_machine):
+ create_mock_trades_usdt(fee)
+ start = datetime.now(timezone.utc)
+ time_machine.move_to(start, tick=False)
+ set_startup_time()
+
+ assert KeyValueStore.get_value("startup_time") == start
+ initial_time = KeyValueStore.get_value("bot_start_time")
+ assert initial_time <= start
+
+ # Simulate bot restart
+ new_start = start + timedelta(days=5)
+ time_machine.move_to(new_start, tick=False)
+ set_startup_time()
+
+ assert KeyValueStore.get_value("startup_time") == new_start
+ assert KeyValueStore.get_value("bot_start_time") == initial_time
diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py
index 23ec6d4fb..948973ed5 100644
--- a/tests/persistence/test_persistence.py
+++ b/tests/persistence/test_persistence.py
@@ -6,7 +6,7 @@ import arrow
import pytest
from sqlalchemy import select
-from freqtrade.constants import DATETIME_PRINT_FORMAT
+from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
@@ -2037,6 +2037,7 @@ def test_Trade_object_idem():
'get_mix_tag_performance',
'get_trading_volume',
'from_json',
+ 'validate_string_len',
)
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
'total_profit')
@@ -2055,6 +2056,31 @@ def test_Trade_object_idem():
assert item in trade
+@pytest.mark.usefixtures("init_persistence")
+def test_trade_truncates_string_fields():
+ trade = Trade(
+ pair='ADA/USDT',
+ stake_amount=20.0,
+ amount=30.0,
+ open_rate=2.0,
+ open_date=datetime.utcnow() - timedelta(minutes=20),
+ fee_open=0.001,
+ fee_close=0.001,
+ exchange='binance',
+ leverage=1.0,
+ trading_mode='futures',
+ enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2,
+ exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2,
+ )
+ Trade.session.add(trade)
+ Trade.commit()
+
+ trade1 = Trade.session.scalars(select(Trade)).first()
+
+ assert trade1.enter_tag == 'a' * CUSTOM_TAG_MAX_LENGTH
+ assert trade1.exit_reason == 'b' * CUSTOM_TAG_MAX_LENGTH
+
+
def test_recalc_trade_from_orders(fee):
o1_amount = 100
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 31075e514..58c904838 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -883,6 +883,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
'trading_volume': expected['trading_volume'],
+ 'bot_start_timestamp': 0,
+ 'bot_start_date': '',
}
@@ -1403,10 +1405,10 @@ def test_api_pair_candles(botclient, ohlcv_history):
])
-def test_api_pair_history(botclient, ohlcv_history):
+def test_api_pair_history(botclient, mocker):
ftbot, client = botclient
timeframe = '5m'
-
+ lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
@@ -1440,6 +1442,7 @@ def test_api_pair_history(botclient, ohlcv_history):
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
+ assert lfm.call_count == 1
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py
index 21c8b0813..f0bb72fc9 100644
--- a/tests/rpc/test_rpc_manager.py
+++ b/tests/rpc/test_rpc_manager.py
@@ -28,6 +28,7 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
+ default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
@@ -52,6 +53,7 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
+ default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
@@ -85,7 +87,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', side_effect=ValueError())
-
+ default_conf['telegram']['enabled'] = True
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -99,6 +101,7 @@ def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
+ default_conf['telegram']['enabled'] = True
default_conf['telegram']['allow_custom_messages'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
@@ -115,9 +118,9 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
+ default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
-
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -166,7 +169,8 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
caplog)
-def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
+def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
+ default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 54f612c59..7978a2a23 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -36,6 +36,13 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades,
patch_exchange, patch_get_signal, patch_whitelist)
+@pytest.fixture
+def default_conf(default_conf) -> dict:
+ # Telegram is enabled by default
+ default_conf['telegram']['enabled'] = True
+ return default_conf
+
+
class DummyCls(Telegram):
"""
Dummy class for testing the Telegram @authorized_only decorator
@@ -2241,8 +2248,9 @@ def test_send_msg_buy_notification_no_fiat(
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
- default_conf, mocker, direction, enter_signal, leverage) -> None:
+ default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
del default_conf['fiat_display_currency']
+ time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
diff --git a/tests/strategy/strats/freqai_test_classifier.py b/tests/strategy/strats/freqai_test_classifier.py
index 61b9f0c37..a68a87b2a 100644
--- a/tests/strategy/strats/freqai_test_classifier.py
+++ b/tests/strategy/strats/freqai_test_classifier.py
@@ -82,7 +82,7 @@ class freqai_test_classifier(IStrategy):
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
-
+ self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
dataframe["close"], 'up', 'down')
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index 7b1399507..204fa996d 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -9,6 +9,7 @@ import pytest
from pandas import DataFrame
from freqtrade.configuration import TimeRange
+from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
@@ -529,13 +530,13 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
assert res[0].exit_reason == 'hello world'
caplog.clear()
- strategy.custom_exit = MagicMock(return_value='h' * 100)
+ strategy.custom_exit = MagicMock(return_value='h' * CUSTOM_TAG_MAX_LENGTH * 2)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_flag is True
- assert res[0].exit_reason == 'h' * 64
+ assert res[0].exit_reason == 'h' * (CUSTOM_TAG_MAX_LENGTH)
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
@@ -986,7 +987,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
}
- mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ return_value=expected_result)
PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.stoploss == -0.05
@@ -1005,11 +1007,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
- mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ return_value=expected_result)
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
StrategyResolver.load_strategy(default_conf)
- mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ MagicMock(side_effect=ValueError()))
StrategyResolver.load_strategy(default_conf)
assert log_has("Invalid parameter file format.", caplog)
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index aab868bec..c445b989d 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -23,7 +23,8 @@ from freqtrade.configuration.load_config import (load_config_file, load_file, lo
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
-from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
+from freqtrade.loggers import (FTBufferingHandler, FTStdErrStreamHandler, _set_loggers,
+ setup_logging, setup_logging_pre)
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
patched_configuration_load_config_file)
@@ -658,7 +659,7 @@ def test_set_loggers_syslog():
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -681,7 +682,7 @@ def test_set_loggers_Filehandler(tmpdir):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -706,7 +707,7 @@ def test_set_loggers_journald(mocker):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
# reset handlers to not break pytest
logger.handlers = orig_handlers
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index bf3cc6ab8..7bded0f82 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -356,7 +356,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
- (0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
+ (0.042, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
@@ -1060,9 +1060,19 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order,
@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(f'{EXMS}.fetch_order', return_value=order)
@@ -1074,8 +1084,10 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
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
@@ -1091,7 +1103,8 @@ 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)
@@ -1116,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
@@ -1129,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(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'})
+ 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()
+ 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,
@@ -1175,7 +1207,7 @@ 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,
@@ -1197,7 +1229,8 @@ 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(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
@@ -1217,21 +1250,36 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# 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())
+ Trade.commit()
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled)
@@ -1242,6 +1290,137 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange_partial(
+ mocker, default_conf_usdt, fee, is_short, limit_order) -> None:
+ stop_order_dict = {'id': "101", "status": "open"}
+ 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(
+ EXMS,
+ fetch_ticker=MagicMock(return_value={
+ 'bid': 1.9,
+ 'ask': 2.2,
+ 'last': 1.9
+ }),
+ create_order=MagicMock(side_effect=[
+ enter_order,
+ exit_order,
+ ]),
+ get_fee=fee,
+ create_stoploss=stoploss
+ )
+ freqtrade = FreqtradeBot(default_conf_usdt)
+ 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.is_open = True
+ trade.open_order_id = None
+ trade.stoploss_order_id = None
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ assert stoploss.call_count == 1
+ assert trade.stoploss_order_id == "101"
+ assert trade.amount == 30
+ stop_order_dict.update({'id': "102"})
+ # Stoploss on exchange is cancelled on exchange, but filled partially.
+ # Must update trade amount to guarantee successful exit.
+ stoploss_order_hit = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'canceled',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': trade.amount / 2,
+ 'remaining': trade.amount / 2,
+ 'amount': enter_order['amount'],
+ })
+ mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ # Stoploss filled partially ...
+ assert trade.amount == 15
+
+ assert trade.stoploss_order_id == "102"
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange_partial_cancel_here(
+ mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None:
+ stop_order_dict = {'id': "101", "status": "open"}
+ default_conf_usdt['trailing_stop'] = True
+ 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(
+ EXMS,
+ fetch_ticker=MagicMock(return_value={
+ 'bid': 1.9,
+ 'ask': 2.2,
+ 'last': 1.9
+ }),
+ create_order=MagicMock(side_effect=[
+ enter_order,
+ exit_order,
+ ]),
+ get_fee=fee,
+ create_stoploss=stoploss
+ )
+ freqtrade = FreqtradeBot(default_conf_usdt)
+ 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.is_open = True
+ trade.open_order_id = None
+ trade.stoploss_order_id = None
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ assert stoploss.call_count == 1
+ assert trade.stoploss_order_id == "101"
+ assert trade.amount == 30
+ stop_order_dict.update({'id': "102"})
+ # Stoploss on exchange is open.
+ # Freqtrade cancels the stop - but cancel returns a partial filled order.
+ stoploss_order_hit = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'open',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': 0,
+ 'remaining': trade.amount,
+ 'amount': enter_order['amount'],
+ })
+ stoploss_order_cancel = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'canceled',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': trade.amount / 2,
+ 'remaining': trade.amount / 2,
+ 'amount': enter_order['amount'],
+ })
+ mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
+ mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
+ trade.stoploss_last_update = arrow.utcnow().shift(minutes=-10).datetime
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ # Canceled Stoploss filled partially ...
+ assert log_has_re('Cancelling current stoploss on exchange.*', caplog)
+
+ assert trade.stoploss_order_id == "102"
+ assert trade.amount == 15
+
+
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
@@ -1273,10 +1452,21 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
- trade.is_short = is_short
+ 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
@@ -1395,7 +1585,7 @@ 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(
EXMS,
@@ -1442,6 +1632,16 @@ def test_handle_stoploss_on_exchange_trailing(
trade.open_order_id = None
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',
@@ -1471,7 +1671,7 @@ def test_handle_stoploss_on_exchange_trailing(
)
cancel_order_mock = MagicMock()
- stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
+ 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)
@@ -1520,7 +1720,7 @@ 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(
@@ -1602,7 +1802,7 @@ def test_stoploss_on_exchange_price_rounding(
EXMS,
get_fee=fee,
)
- price_mock = MagicMock(side_effect=lambda p, s: int(s))
+ price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s))
stoploss_mock = MagicMock(return_value={'id': '13434334'})
adjust_mock = MagicMock(return_value=False)
mocker.patch.multiple(
@@ -1629,7 +1829,7 @@ 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(
EXMS,
@@ -1676,6 +1876,16 @@ def test_handle_stoploss_on_exchange_custom_stop(
trade.open_order_id = None
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',
@@ -1704,7 +1914,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
)
cancel_order_mock = MagicMock()
- stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
+ 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'
@@ -1753,7 +1963,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)
@@ -1802,11 +2012,21 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
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,
@@ -1853,7 +2073,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',
@@ -2735,6 +2955,9 @@ def test_manage_open_orders_exit_usercustom(
assert rpc_mock.call_count == 2
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
+ trade = Trade.session.scalars(select(Trade)).first()
+ # cancelling didn't succeed - order-id remains open.
+ assert trade.open_order_id is not None
# 2nd canceled trade - Fail execute exit
caplog.clear()
@@ -3245,6 +3468,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
# TODO: should not be magicmock
trade = MagicMock()
+ trade.open_order_id = '125'
reason = CANCEL_REASON['TIMEOUT']
order = {'remaining': 1,
'id': '125',
@@ -3252,6 +3476,10 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
'status': "open"}
assert not freqtrade.handle_cancel_exit(trade, order, reason)
+ # mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
+ # assert not freqtrade.handle_cancel_exit(trade, order, reason)
+ # assert trade.open_order_id == '125'
+
@pytest.mark.parametrize("is_short, open_rate, amt", [
(False, 2.0, 30.0),
@@ -3606,10 +3834,12 @@ 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(
@@ -3707,12 +3937,12 @@ 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,
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 5cbedd818..9fb9fd8b3 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -35,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,
@@ -49,8 +49,9 @@ 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=[
[],
@@ -93,13 +94,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
wallets_mock.reset_mock()
trades = Trade.session.scalars(select(Trade)).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')
+ # 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)
diff --git a/tests/test_timerange.py b/tests/test_timerange.py
index 06ff1983a..993b24d95 100644
--- a/tests/test_timerange.py
+++ b/tests/test_timerange.py
@@ -10,6 +10,8 @@ from freqtrade.exceptions import OperationalException
def test_parse_timerange_incorrect():
+ timerange = TimeRange.parse_timerange('')
+ assert timerange == TimeRange(None, None, 0, 0)
timerange = TimeRange.parse_timerange('20100522-')
assert TimeRange('date', None, 1274486400, 0) == timerange
assert timerange.timerange_str == '20100522-'