Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
jainanuj94 2024-08-01 23:55:01 +05:30
commit 2ad921f99e
31 changed files with 569 additions and 558 deletions

View File

@ -55,7 +55,7 @@ jobs:
- name: Installation - *nix
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip 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
@ -197,7 +197,7 @@ jobs:
- name: Installation (python)
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip 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
@ -427,7 +427,7 @@ jobs:
- name: Installation - *nix
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip 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

View File

@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.5.4'
rev: 'v0.5.5'
hooks:
- id: ruff

View File

@ -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<=24.0" wheel
&& pip install --upgrade pip wheel
# Install TA-lib
COPY build_helpers/* /tmp/

View File

@ -1,6 +1,6 @@
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"

View File

@ -9,11 +9,6 @@
],
"minimum": -1
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30
},
"timeframe": {
"description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.",
"type": "string"
@ -562,6 +557,7 @@
"enum": [
"StaticPairList",
"VolumePairList",
"PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",
@ -1064,7 +1060,7 @@
"default": {},
"properties": {
"process_throttle_secs": {
"description": "Throttle time in seconds for processing.",
"description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer"
},
"interval": {
@ -1105,6 +1101,15 @@
"description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.",
"type": "boolean"
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30
},
"download_trades": {
"description": "Download trades data by default (instead of ohlcv data).",
"type": "boolean"
},
"max_entry_position_adjustment": {
"description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.",
"type": [

View File

@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": 0.05,

View File

@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"trading_mode": "futures",
"margin_mode": "isolated",
"max_open_trades": 5,

View File

@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "BTC",
"stake_amount": 0.05,

View File

@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 5,
"stake_currency": "EUR",
"stake_amount": 10,

View File

@ -17,7 +17,7 @@ RUN mkdir /freqtrade \
&& chown ftuser:ftuser /freqtrade \
# Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
&& pip install --upgrade "pip<=24.0"
&& pip install --upgrade pip
WORKDIR /freqtrade

View File

@ -2,11 +2,11 @@
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings.
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers).
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList` or `PercentChangePairList` as the starting Pairlist Handler.
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
@ -22,6 +22,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list)
* [`PercentChangePairList`](#percent-change-pair-list)
* [`ProducerPairList`](#producerpairlist)
* [`RemotePairList`](#remotepairlist)
* [`MarketCapPairList`](#marketcappairlist)
@ -152,6 +153,89 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
!!! Note
`VolumePairList` does not support backtesting mode.
#### Percent Change Pair List
`PercentChangePairList` filters and sorts pairs based on the percentage change in their price over the last 24 hours or any defined timeframe as part of advanced options. This allows traders to focus on assets that have experienced significant price movements, either positive or negative.
**Configuration Options**
* `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change.
* `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out.
* `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out.
* `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order.
* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes).
* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day.
* `lookback_timeframe`: Timeframe to use for the lookback period.
* `lookback_period`: Number of periods to look back at.
When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency.
`PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library:
The percentage change is calculated as the change in price over the last 24 hours.
??? Note "Unsupported exchanges"
On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that these pairlists will only refresh once per day.
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 20,
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 1
}
],
```
**Example Configuration to Read from Ticker**
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"min_value": -10,
"max_value": 50
}
],
```
In this configuration:
1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours.
2. Only pairs with a percentage change between -10% and 50% are considered.
**Example Configuration to Read from Candles**
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 3600,
"lookback_timeframe": "1h",
"lookback_period": 72
}
],
```
This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles.
The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period:
$$ Percent Change = (\frac{Current Close - Previous Close}{Previous Close}) * 100 $$
!!! Warning "Range look back and refresh period"
When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API.
!!! Warning "Performance implications when using lookback range"
If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further percent-change calculation.
!!! Note "Backtesting"
`PercentChangePairList` does not support backtesting mode.
#### ProducerPairList
With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer.

View File

@ -1,6 +1,6 @@
markdown==3.6
mkdocs==1.6.0
mkdocs-material==9.5.29
mkdocs-material==9.5.30
mdx_truly_sane_lists==1.3
pymdown-extensions==10.8.1
pymdown-extensions==10.9
jinja2==3.1.4

View File

@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2024.7-dev"
__version__ = "2024.8-dev"
if "dev" in __version__:
from pathlib import Path

View File

@ -36,11 +36,6 @@ CONF_SCHEMA = {
"type": ["integer", "number"],
"minimum": -1,
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30,
},
"timeframe": {
"description": (
f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}"
@ -185,6 +180,7 @@ CONF_SCHEMA = {
"type": "boolean",
"default": False,
},
# Lookahead analysis section
"minimum_trade_amount": {
"description": "Minimum amount for a trade - only used for lookahead-analysis",
"type": "number",
@ -501,6 +497,7 @@ CONF_SCHEMA = {
"required": ["method"],
},
},
# RPC section
"telegram": {
"description": "Telegram settings.",
"type": "object",
@ -701,6 +698,7 @@ CONF_SCHEMA = {
},
"required": ["enabled", "listen_ip_address", "listen_port", "username", "password"],
},
# end of RPC section
"db_url": {
"description": "Database connection URL.",
"type": "string",
@ -734,7 +732,7 @@ CONF_SCHEMA = {
"default": {},
"properties": {
"process_throttle_secs": {
"description": "Throttle time in seconds for processing.",
"description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer",
},
"interval": {
@ -763,6 +761,16 @@ CONF_SCHEMA = {
"description": f"Enable position adjustment. {__IN_STRATEGY}",
"type": "boolean",
},
# Download data section
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30,
},
"download_trades": {
"description": "Download trades data by default (instead of ohlcv data).",
"type": "boolean",
},
"max_entry_position_adjustment": {
"description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}",
"type": ["integer", "number"],

View File

@ -42,7 +42,7 @@ HYPEROPT_LOSS_BUILTIN = [
AVAILABLE_PAIRLISTS = [
"StaticPairList",
"VolumePairList",
"PercentVolumeChangePairList",
"PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",

View File

@ -15819,104 +15819,6 @@
}
}
],
"GAL/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 11.0,
"info": {
"bracket": "1",
"initialLeverage": "11",
"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": 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": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1",
"cum": "5650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 2.0,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "11900.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 3000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "3000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "386900.0"
}
}
],
"GALA/USDT:USDT": [
{
"tier": 1.0,
@ -28526,6 +28428,250 @@
}
],
"REEF/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 26.0,
"info": {
"bracket": "1",
"initialLeverage": "26",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 20000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "2",
"initialLeverage": "25",
"notionalCap": "20000",
"notionalFloor": "5000",
"maintMarginRatio": "0.02",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 20000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "20000",
"maintMarginRatio": "0.025",
"cum": "125.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 200000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "10",
"notionalCap": "200000",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "750.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 200000.0,
"maxNotional": 400000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "5",
"notionalCap": "400000",
"notionalFloor": "200000",
"maintMarginRatio": "0.1",
"cum": "10750.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 400000.0,
"maxNotional": 500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "6",
"initialLeverage": "4",
"notionalCap": "500000",
"notionalFloor": "400000",
"maintMarginRatio": "0.125",
"cum": "20750.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
"minNotional": 500000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.25",
"cum": "83250.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 1500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
"notionalCap": "1500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
"cum": "333250.0"
}
}
],
"REN/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "1",
"initialLeverage": "50",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "50000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 600000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "600000",
"notionalFloor": "50000",
"maintMarginRatio": "0.05",
"cum": "1300.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": "31300.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": "71300.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": "321300.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": "1821300.0"
}
}
],
"RENDER/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@ -28655,120 +28801,6 @@
}
}
],
"REN/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "1",
"initialLeverage": "50",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "50000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 600000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "600000",
"notionalFloor": "50000",
"maintMarginRatio": "0.05",
"cum": "1300.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": "31300.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": "71300.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": "321300.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": "1821300.0"
}
}
],
"REZ/USDT:USDT": [
{
"tier": 1.0,
@ -29127,136 +29159,6 @@
}
}
],
"RNDR/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 26.0,
"info": {
"bracket": "1",
"initialLeverage": "26",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "2",
"initialLeverage": "25",
"notionalCap": "50000",
"notionalFloor": "5000",
"maintMarginRatio": "0.02",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "20",
"notionalCap": "100000",
"notionalFloor": "50000",
"maintMarginRatio": "0.025",
"cum": "275.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "10",
"notionalCap": "1000000",
"notionalFloor": "100000",
"maintMarginRatio": "0.05",
"cum": "2775.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 2000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "5",
"notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.1",
"cum": "52775.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 2000000.0,
"maxNotional": 2500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "6",
"initialLeverage": "4",
"notionalCap": "2500000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.125",
"cum": "102775.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
"minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
"notionalCap": "5000000",
"notionalFloor": "2500000",
"maintMarginRatio": "0.25",
"cum": "415275.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
"minNotional": 5000000.0,
"maxNotional": 6000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
"notionalCap": "6000000",
"notionalFloor": "5000000",
"maintMarginRatio": "0.5",
"cum": "1665275.0"
}
}
],
"RONIN/USDT:USDT": [
{
"tier": 1.0,

View File

@ -128,6 +128,7 @@ class Exchange:
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote"
"tickers_have_quoteVolume": True,
"tickers_have_percentage": True,
"tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers
"tickers_have_price": True,
"trades_limit": 1000, # Limit for 1 call to fetch_trades

View File

@ -17,7 +17,7 @@ class Hyperliquid(Exchange):
_ft_has: Dict = {
# Only the most recent 5000 candles are available according to the
# exchange's API documentation.
"ohlcv_has_history": True,
"ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"trades_has_history": False, # Trades endpoint doesn't seem available.
"exchange_has_overrides": {"fetchTrades": False},

View File

@ -12,6 +12,7 @@ class Ticker(TypedDict):
last: Optional[float]
quoteVolume: Optional[float]
baseVolume: Optional[float]
percentage: Optional[float]
# Several more - only listing required.

View File

@ -1,30 +1,30 @@
"""
Change PairList provider
Percent Change PairList provider
Provides dynamic pair list based on trade change
sorted based on percentage change in volume over a
defined period
sorted based on percentage change in price over a
defined period or as coming from ticker
"""
import logging
from datetime import timedelta
from typing import Any, Dict, List, Literal
from typing import Any, Dict, List, Optional
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.types import Tickers
from freqtrade.exchange.types import Ticker, Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_now, format_ms_time
logger = logging.getLogger(__name__)
SORT_VALUES = ["rolling_volume_change"]
class PercentVolumeChangePairList(IPairList):
class PercentChangePairList(IPairList):
is_pairlist_generator = True
supports_backtesting = SupportsBacktesting.NO
@ -39,16 +39,14 @@ class PercentVolumeChangePairList(IPairList):
self._stake_currency = self._config["stake_currency"]
self._number_pairs = self._pairlistconfig["number_assets"]
self._sort_key: Literal["rolling_volume_change"] = self._pairlistconfig.get(
"sort_key", "rolling_volume_change"
)
self._min_value = self._pairlistconfig.get("min_value", 0)
self._min_value = self._pairlistconfig.get("min_value", None)
self._max_value = self._pairlistconfig.get("max_value", None)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)
self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", "desc")
self._def_candletype = self._config["candle_type_def"]
if (self._lookback_days > 0) & (self._lookback_period > 0):
@ -79,23 +77,18 @@ class PercentVolumeChangePairList(IPairList):
if not self._use_range and not (
self._exchange.exchange_has("fetchTickers")
and self._exchange.get_option("tickers_have_change")
and self._exchange.get_option("tickers_have_percentage")
):
raise OperationalException(
"Exchange does not support dynamic whitelist in this configuration. "
"Please edit your config and either remove PercentVolumeChangePairList, "
"Please edit your config and either remove PercentChangePairList, "
"or switch to using candles. and restart the bot."
)
if not self._validate_keys(self._sort_key):
raise OperationalException(f"key {self._sort_key} not in {SORT_VALUES}")
candle_limit = self._exchange.ohlcv_candle_limit(
self._lookback_timeframe, self._config["candle_type_def"]
)
if self._lookback_period < 4:
raise OperationalException("ChangeFilter requires lookback_period to be >= 4")
self.log_once(f"Candle limit is {candle_limit}", logger.info)
if self._lookback_period > candle_limit:
raise OperationalException(
"ChangeFilter requires lookback_period to not "
@ -111,19 +104,15 @@ class PercentVolumeChangePairList(IPairList):
"""
return not self._use_range
def _validate_keys(self, key):
return key in SORT_VALUES
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
return (f"{self.name} - top {self._pairlistconfig['number_assets']} percent "
f"volume change pairs.")
return f"{self.name} - top {self._pairlistconfig['number_assets']} percent change pairs."
@staticmethod
def description() -> str:
return "Provides dynamic pair list based on percentage volume change."
return "Provides dynamic pair list based on percentage change."
@staticmethod
def available_parameters() -> Dict[str, PairlistParameter]:
@ -134,16 +123,9 @@ class PercentVolumeChangePairList(IPairList):
"description": "Number of assets",
"help": "Number of assets to use from the pairlist",
},
"sort_key": {
"type": "option",
"default": "rolling_volume_change",
"options": SORT_VALUES,
"description": "Sort key",
"help": "Sort key to use for sorting the pairlist.",
},
"min_value": {
"type": "number",
"default": 0,
"default": None,
"description": "Minimum value",
"help": "Minimum value to use for filtering the pairlist.",
},
@ -153,12 +135,14 @@ class PercentVolumeChangePairList(IPairList):
"description": "Maximum value",
"help": "Maximum value to use for filtering the pairlist.",
},
"refresh_period": {
"type": "number",
"default": 1800,
"description": "Refresh period",
"help": "Refresh period in seconds",
"sort_direction": {
"type": "option",
"default": "desc",
"options": ["", "asc", "desc"],
"description": "Sort pairlist",
"help": "Sort Pairlist ascending or descending by rate of change.",
},
**IPairList.refresh_period_parameter(),
"lookback_days": {
"type": "number",
"default": 0,
@ -185,8 +169,6 @@ class PercentVolumeChangePairList(IPairList):
:param tickers: Tickers (from exchange.get_tickers). May be cached.
:return: List of pairs
"""
# Generate dynamic whitelist
# Must always run if this pairlist is not the first in the list.
pairlist = self._pair_cache.get("pairlist")
if pairlist:
# Item found - no refresh necessary
@ -209,7 +191,7 @@ class PercentVolumeChangePairList(IPairList):
for k, v in tickers.items()
if (
self._exchange.get_pair_quote_currency(k) == self._stake_currency
and (self._use_range or v.get(self._sort_key) is not None)
and (self._use_range or v.get("percentage") is not None)
and v["symbol"] in _pairlist
)
]
@ -230,83 +212,25 @@ class PercentVolumeChangePairList(IPairList):
:param tickers: Tickers (from exchange.get_tickers). May be cached.
:return: new whitelist
"""
self.log_once(f"Filter ticker is self use range {pairlist}", logger.warning)
filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist]
if self._use_range:
filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist]
# get lookback period in ms, for exchange ohlcv fetch
since_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe,
dt_now()
+ timedelta(
minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min
),
).timestamp()
)
* 1000
)
to_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min)
).timestamp()
)
* 1000
)
# todo: utc date output for starting date
self.log_once(
f"Using change range of {self._lookback_period} candles, timeframe: "
f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} "
f"till {format_ms_time(to_ms)}",
logger.info,
)
needed_pairs: ListPairsWithTimeframes = [
(p, self._lookback_timeframe, self._def_candletype)
for p in [s["symbol"] for s in filtered_tickers]
if p not in self._pair_cache
]
candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms)
for i, p in enumerate(filtered_tickers):
pair_candles = (
candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)]
if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles
else None
)
# in case of candle data calculate typical price and change for candle
if pair_candles is not None and not pair_candles.empty:
pair_candles["rolling_volume_sum"] = (
pair_candles["volume"].rolling(window=self._lookback_period).sum()
)
pair_candles["rolling_volume_change"] = (
pair_candles["rolling_volume_sum"].pct_change() * 100
)
# ensure that a rolling sum over the lookback_period is built
# if pair_candles contains more candles than lookback_period
rolling_volume_change = pair_candles["rolling_volume_change"].fillna(0).iloc[-1]
# replace change with a range change sum calculated above
filtered_tickers[i]["rolling_volume_change"] = rolling_volume_change
self.log_once(f"ticker {filtered_tickers[i]}", logger.info)
else:
filtered_tickers[i]["rolling_volume_change"] = 0
# calculating using lookback_period
self.fetch_percent_change_from_lookback_period(filtered_tickers)
else:
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
# Fetching 24h change by default from supported exchange tickers
self.fetch_percent_change_from_tickers(filtered_tickers, tickers)
filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] > self._min_value]
if self._min_value is not None:
filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value]
if self._max_value is not None:
filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] < self._max_value]
filtered_tickers = [v for v in filtered_tickers if v["percentage"] < self._max_value]
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key])
sorted_tickers = sorted(
filtered_tickers,
reverse=self._sort_direction == "desc",
key=lambda t: t["percentage"],
)
self.log_once(f"Sorted Tickers {sorted_tickers}", logger.info)
# Validate whitelist to only have active market pairs
pairs = self._whitelist_for_active_markets([s["symbol"] for s in sorted_tickers])
pairs = self.verify_blacklist(pairs, logmethod=logger.info)
@ -314,3 +238,92 @@ class PercentVolumeChangePairList(IPairList):
pairs = pairs[: self._number_pairs]
return pairs
def fetch_candles_for_lookback_period(
self, filtered_tickers: List[Dict[str, str]]
) -> Dict[PairWithTimeframe, DataFrame]:
since_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe,
dt_now()
+ timedelta(
minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min
),
).timestamp()
)
* 1000
)
to_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min)
).timestamp()
)
* 1000
)
# todo: utc date output for starting date
self.log_once(
f"Using change range of {self._lookback_period} candles, timeframe: "
f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} "
f"till {format_ms_time(to_ms)}",
logger.info,
)
needed_pairs: ListPairsWithTimeframes = [
(p, self._lookback_timeframe, self._def_candletype)
for p in [s["symbol"] for s in filtered_tickers]
if p not in self._pair_cache
]
candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms)
return candles
def fetch_percent_change_from_lookback_period(self, filtered_tickers: List[Dict[str, Any]]):
# get lookback period in ms, for exchange ohlcv fetch
candles = self.fetch_candles_for_lookback_period(filtered_tickers)
for i, p in enumerate(filtered_tickers):
pair_candles = (
candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)]
if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles
else None
)
# in case of candle data calculate typical price and change for candle
if pair_candles is not None and not pair_candles.empty:
current_close = pair_candles["close"].iloc[-1]
previous_close = pair_candles["close"].shift(self._lookback_period).iloc[-1]
pct_change = (
((current_close - previous_close) / previous_close) if previous_close > 0 else 0
)
# replace change with a range change sum calculated above
filtered_tickers[i]["percentage"] = pct_change
else:
filtered_tickers[i]["percentage"] = 0
def fetch_percent_change_from_tickers(self, filtered_tickers: List[Dict[str, Any]], tickers):
for i, p in enumerate(filtered_tickers):
# Filter out assets
if not self._validate_pair(
p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None
):
filtered_tickers.remove(p)
else:
filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"]
def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
"""
Check if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_ticker
:return: True if the pair can stay, false if it should be removed
"""
if not ticker or "percentage" not in ticker or ticker["percentage"] is None:
self.log_once(
f"Removed {pair} from whitelist, because "
"ticker['percentage'] is empty (Usually no trade in the last 24h).",
logger.info,
)
return False
return True

View File

@ -6,6 +6,7 @@
"refresh_period": 1800
}' %}
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": {{ max_open_trades }},
"stake_currency": "{{ stake_currency }}",
"stake_amount": {{ stake_amount }},

View File

@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2024.7-dev"
__version__ = "2024.8-dev"
if "dev" in __version__:
from pathlib import Path

View File

@ -1,3 +1,3 @@
# Requirements for freqtrade client library
requests==2.32.3
python-rapidjson==1.18
python-rapidjson==1.19

View File

@ -7,10 +7,10 @@
-r docs/requirements-docs.txt
coveralls==4.0.1
ruff==0.5.4
ruff==0.5.5
mypy==1.11.0
pre-commit==3.7.1
pytest==8.3.1
pre-commit==3.8.0
pytest==8.3.2
pytest-asyncio==0.23.8
pytest-cov==5.0.0
pytest-mock==3.14.0

View File

@ -6,7 +6,7 @@
scikit-learn==1.5.1
joblib==1.4.2
catboost==1.2.5; 'arm' not in platform_machine
lightgbm==4.4.0
lightgbm==4.5.0
xgboost==2.0.3
tensorboard==2.17.0
datasieve==0.1.7

View File

@ -1,4 +1,4 @@
# Include all requirements to run the bot.
-r requirements.txt
plotly==5.22.0
plotly==5.23.0

View File

@ -4,7 +4,7 @@ bottleneck==1.4.0
numexpr==2.10.1
pandas-ta==0.3.14b
ccxt==4.3.65
ccxt==4.3.68
cryptography==43.0.0
aiohttp==3.9.5
SQLAlchemy==2.0.31
@ -30,7 +30,7 @@ pyarrow==17.0.0; platform_machine != 'armv7l'
py_find_1st==1.1.6
# Load ticker files 30% faster
python-rapidjson==1.18
python-rapidjson==1.19
# Properly format api responses
orjson==3.10.6

View File

@ -49,7 +49,7 @@ function updateenv() {
source .venv/bin/activate
SYS_ARCH=$(uname -m)
echo "pip install in-progress. Please wait..."
${PYTHON} -m pip install --upgrade "pip<=24.0" wheel setuptools
${PYTHON} -m pip install --upgrade pip wheel setuptools
REQUIREMENTS_HYPEROPT=""
REQUIREMENTS_PLOT=""
REQUIREMENTS_FREQAI=""

View File

@ -2188,7 +2188,7 @@ def tickers():
"first": None,
"last": 530.21,
"change": 0.558,
"percentage": None,
"percentage": 2.349,
"average": None,
"baseVolume": 72300.0659,
"quoteVolume": 37670097.3022171,

View File

@ -31,11 +31,9 @@ from tests.conftest import (
)
# Exclude RemotePairList and PercentVolumeChangePairList from tests.
# They have mandatory parameters, and requires special handling,
# which happens in test_remotepairlist and test_percentvolumechangepairlist.
TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS
if p not in ["RemotePairList", "PercentVolumeChangePairList"]]
# Exclude RemotePairList from tests.
# It has a mandatory parameter, and requires special handling, which happens in test_remotepairlist.
TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairList"]]
@pytest.fixture(scope="function")

View File

@ -7,7 +7,7 @@ import pytest
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.enums import CandleType
from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.PercentVolumeChangePairList import PercentVolumeChangePairList
from freqtrade.plugins.pairlist.PercentChangePairList import PercentChangePairList
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import (
EXMS,
@ -33,9 +33,9 @@ def rpl_config(default_conf):
def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
}
@ -44,7 +44,7 @@ def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config):
with pytest.raises(
OperationalException,
match=r"Exchange does not support dynamic whitelist in this configuration. "
r"Please edit your config and either remove PercentVolumeChangePairList, "
r"Please edit your config and either remove PercentChangePairList, "
r"or switch to using candles. and restart the bot.",
):
get_patched_freqtradebot(mocker, rpl_config)
@ -53,9 +53,9 @@ def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config):
def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 1800,
"lookback_days": 4,
@ -74,9 +74,9 @@ def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config):
def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 3,
@ -95,41 +95,9 @@ def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 3,
}
]
with pytest.raises(
OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4"
):
get_patched_freqtradebot(mocker, rpl_config)
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"min_value": 0,
"refresh_period": 86400,
"lookback_period": 3,
}
]
with pytest.raises(
OperationalException, match=r"ChangeFilter requires lookback_period to be >= 4"
):
get_patched_freqtradebot(mocker, rpl_config)
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 1001,
@ -147,8 +115,8 @@ def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config):
def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"sort_key": "rolling_volume_change",
"method": "PercentChangePairList",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
}
@ -165,9 +133,9 @@ def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config):
def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tickers, time_machine):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 4,
@ -210,7 +178,7 @@ def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tic
)
),
("TKN/USDT", "1d", CandleType.SPOT): pd.DataFrame(
# Make sure always have highest rolling_volume_change
# Make sure always have highest percentage
{
"timestamp": [
"2024-07-01 00:00:00",
@ -234,24 +202,25 @@ def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tic
exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = PercentVolumeChangePairList(
remote_pairlist = PercentChangePairList(
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
)
result = remote_pairlist.gen_pairlist(tickers)
assert len(result) == 2
assert result == ["TKN/USDT", "BTC/USDT"]
assert result == ["NEO/USDT", "TKN/USDT"]
def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_machine):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 86400,
"sort_direction": "asc",
"lookback_days": 4,
}
]
@ -272,7 +241,7 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac
"open": [100, 102, 101, 103, 104, 105],
"high": [102, 103, 102, 104, 105, 106],
"low": [99, 101, 100, 102, 103, 104],
"close": [101, 102, 103, 104, 105, 106],
"close": [101, 102, 103, 104, 105, 105],
"volume": [1000, 1500, 2000, 2500, 3000, 3500],
}
),
@ -289,8 +258,8 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac
"open": [100, 102, 101, 103, 104, 105],
"high": [102, 103, 102, 104, 105, 106],
"low": [99, 101, 100, 102, 103, 104],
"close": [101, 102, 103, 104, 105, 106],
"volume": [1000, 1500, 2000, 2500, 3000, 3500],
"close": [101, 102, 103, 104, 105, 104],
"volume": [1000, 1500, 2000, 2500, 3000, 3400],
}
),
}
@ -299,22 +268,22 @@ def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_mac
exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = PercentVolumeChangePairList(
remote_pairlist = PercentChangePairList(
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
)
result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {})
assert len(result) == 2
assert result == ["ETH/USDT", "XRP/USDT"]
assert result == ["XRP/USDT", "ETH/USDT"]
def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_machine):
rpl_config["pairlists"] = [
{
"method": "PercentVolumeChangePairList",
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "rolling_volume_change",
"sort_key": "percentage",
"min_value": 0,
"max_value": 15,
"refresh_period": 86400,
@ -356,7 +325,7 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma
"open": [100, 102, 101, 103, 104, 105],
"high": [102, 103, 102, 104, 105, 106],
"low": [99, 101, 100, 102, 103, 104],
"close": [101, 102, 103, 104, 105, 106],
"close": [101, 102, 103, 104, 105, 101],
"volume": [1000, 1500, 2000, 2500, 3000, 3500],
}
),
@ -366,7 +335,7 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma
exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = PercentVolumeChangePairList(
remote_pairlist = PercentChangePairList(
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
)
@ -374,3 +343,28 @@ def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_ma
assert len(result) == 1
assert result == ["ETH/USDT"]
def test_gen_pairlist_from_tickers(mocker, rpl_config, tickers):
rpl_config["pairlists"] = [
{
"method": "PercentChangePairList",
"number_assets": 2,
"sort_key": "percentage",
"min_value": 0,
}
]
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = PercentChangePairList(
exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
)
result = remote_pairlist.gen_pairlist(tickers.return_value)
assert len(result) == 1
assert result == ["ETH/USDT"]