Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2a08881
chore: update example configs for api_key wording
xmatthias Aug 17, 2026
b1c7f27
docs: update documentation to use api_key (over key)
xmatthias Aug 17, 2026
f8ad75c
chore: update config schema to include api_key
xmatthias Aug 17, 2026
ed367cb
test: update test setups for api_key wording
xmatthias Aug 17, 2026
21c8ed8
chore: api_key wording for new-config
xmatthias Aug 17, 2026
f67dec9
test: improve test setup
xmatthias Aug 17, 2026
0ce6e6d
test: update test wording to "api_key" instead of "key"
xmatthias Aug 17, 2026
cd7c808
chore: minor correctness fixes
xmatthias Aug 17, 2026
f76b2fa
test: improved test coverage
xmatthias Aug 17, 2026
1bd75e7
Merge pull request #13467 from freqtrade/api_keys
xmatthias Aug 17, 2026
7805c12
fix: slow cache leak when using short timeframes in pairlists
xmatthias Aug 16, 2026
1792480
test: update test to test for cache growth
xmatthias Aug 16, 2026
0cef9a3
chore: Improved method call ergonomics
xmatthias Aug 16, 2026
125b83e
test: adapt exchange test for new variant
xmatthias Aug 16, 2026
2c4373a
refactor: use existing helpers to calculate since_ms
xmatthias Aug 16, 2026
7307983
feat: support lookback_period for RangeStabilityFilter
xmatthias Aug 16, 2026
6b4dc52
feat: support lookback_period for VolatilityFilter
xmatthias Aug 16, 2026
dadbcb7
docs: update documentation for Lookback and Rangestability filter
xmatthias Aug 16, 2026
a92f7e8
test: update tests for Volatility and RangeStability filters
xmatthias Aug 16, 2026
165c0f8
fix: don't fallback when lookback_timeframe is set
xmatthias Aug 17, 2026
d5a3e41
test: add test for "volumepairlist no range" configs
xmatthias Aug 17, 2026
a35c3ad
refactor: extract lookback_days pairlist resolution
xmatthias Aug 17, 2026
a0a3821
test: necessary adjustments to pairlist tests
xmatthias Aug 17, 2026
bfc74f1
fix: disallow ambiguous config
xmatthias Aug 17, 2026
c550828
refactor: extract pairlist lookback parameter config
xmatthias Aug 17, 2026
b740ad7
test: ensure correct default applies to pairlists
xmatthias Aug 17, 2026
93dc807
chore: improve errors for improved UX
xmatthias Aug 17, 2026
3cfe792
feat: warn about odd configurations
xmatthias Aug 17, 2026
ba634ac
feat: align wording for pairlist lookback ranges
xmatthias Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions build_helpers/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1273,13 +1273,19 @@
"description": "Name of the exchange.",
"type": "string"
},
"api_key": {
"description": "API key for the exchange. Recommended to be set via environment variable FREQTRADE__EXCHANGE__API_KEY",
"type": [
"string",
"null"
]
},
"key": {
"description": "API key for the exchange. Recommended to be set via environment variable FREQTRADE__EXCHANGE__KEY",
"description": "API key for the exchange. Recommended to be set via environment variable FREQTRADE__EXCHANGE__KEY Deprecated, use api_key instead.",
"type": [
"string",
"null"
],
"default": null
]
},
"secret": {
"description": "API secret for the exchange. Recommended to be set via environment variable FREQTRADE__EXCHANGE__SECRET",
Expand Down
2 changes: 1 addition & 1 deletion config_examples/config_binance.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {
Expand Down
4 changes: 2 additions & 2 deletions config_examples/config_freqai.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
Expand Down
2 changes: 1 addition & 1 deletion config_examples/config_full.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
],
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"password": "",
"log_responses": false,
Expand Down
4 changes: 2 additions & 2 deletions config_examples/config_kraken.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
},
"exchange": {
"name": "kraken",
"key": "your_exchange_key",
"secret": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {
},
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
| | **Exchange**
| `exchange.name` | **Required.** Name of the exchange class to use. <br> **Datatype:** String
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.api_key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.uid` | API uid to use for the exchange. Only required when you are in production mode and for exchanges that use uid for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
Expand Down Expand Up @@ -699,7 +699,7 @@ creating trades on the exchange.
```json
"exchange": {
"name": "binance",
"key": "key",
"api_key": "api_key",
"secret": "secret",
...
}
Expand Down Expand Up @@ -749,7 +749,7 @@ API Keys are usually only required for live trading (trading for real money, bot
{
"exchange": {
"name": "binance",
"key": "af8ddd35195e9dc500b9a6f799f6f5c93d89193b",
"api_key": "af8ddd35195e9dc500b9a6f799f6f5c93d89193b",
"secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5",
//"password": "", // Optional, not needed by all exchanges)
// ...
Expand Down
16 changes: 8 additions & 8 deletions docs/exchanges.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ A exchange configuration for "binance" would look as follows:
```json
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {},
Expand All @@ -40,7 +40,7 @@ In case of problems related to rate-limits (usually DDOS Exceptions in your logs
```json
"exchange": {
"name": "kraken",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
Expand Down Expand Up @@ -95,7 +95,7 @@ They can however also be configured via configuration file. Since json doesn't s

``` json
// ...
"key": "<someapikey>",
"api_key": "<someapikey>",
"secret": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBABACAFQA<...>s8KX8=\n-----END PRIVATE KEY-----"
// ...
```
Expand Down Expand Up @@ -224,7 +224,7 @@ Kraken Futures uses the exchange id `krakenfutures` and supports isolated future
```jsonc
"exchange": {
"name": "krakenfutures",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret"
},
"trading_mode": "futures",
Expand All @@ -250,7 +250,7 @@ Kucoin requires a passphrase for each api key, you will therefore need to add th
```json
"exchange": {
"name": "kucoin",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"password": "your_exchange_api_key_password",
// ...
Expand Down Expand Up @@ -283,7 +283,7 @@ OKX requires a passphrase for each api key, you will therefore need to add this
```json
"exchange": {
"name": "okx",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"password": "your_exchange_api_key_password",
// ...
Expand Down Expand Up @@ -364,7 +364,7 @@ Bitget requires a passphrase for each api key, you will therefore need to add th
```json
"exchange": {
"name": "bitget",
"key": "your_exchange_key",
"api_key": "your_exchange_api_key",
"secret": "your_exchange_secret",
"password": "your_exchange_api_key_password",
// ...
Expand Down Expand Up @@ -511,7 +511,7 @@ If your account is required to use an operatorId, you can set it in the configur
``` json
"exchange": {
"name": "bitvavo",
"key": "",
"api_key": "",
"secret": "",
"ccxt_config": {
"options": {
Expand Down
48 changes: 42 additions & 6 deletions docs/includes/pairlists.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
* `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_days`: Number of days to look back. `lookback_days` implies a `lookback_timeframe` of 1 day - combining it with a different `lookback_timeframe` will result in an error.
* `lookback_timeframe`: Timeframe to use for the lookback period.
* `lookback_period`: Number of periods to look back at.

Expand Down Expand Up @@ -635,7 +635,9 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio

#### RangeStabilityFilter

Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change` or above `max_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
Removes pairs where the difference between lowest low and highest high over `lookback_period` candles of `lookback_timeframe` (defaults to `1d`) is below `min_rate_of_change` or above `max_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.

For convenience, `lookback_days` can be used instead, which implies daily candles (equivalent to setting `lookback_period` with a `lookback_timeframe` of `1d`). One of `lookback_days` or `lookback_period` must be set - setting both is ambiguous and will result in an error, as does combining `lookback_days` with a `lookback_timeframe` other than `1d`. Setting neither is deprecated and currently falls back to a lookback of 10 days - this fallback will be removed in a future version.

In the below example:
If the trading range over the last 10 days is <1% or >99%, remove the pair from the whitelist.
Expand All @@ -644,14 +646,30 @@ If the trading range over the last 10 days is <1% or >99%, remove the pair from
"pairlists": [
{
"method": "RangeStabilityFilter",
"lookback_days": 10,
"lookback_timeframe": "1d",
"lookback_period": 10,
"min_rate_of_change": 0.01,
"max_rate_of_change": 0.99,
"refresh_period": 86400
}
]
```

The same filter based on a trading range of 72 1h candles (3 days) would look as follows:

```json
"pairlists": [
{
"method": "RangeStabilityFilter",
"lookback_timeframe": "1h",
"lookback_period": 72,
"min_rate_of_change": 0.01,
"max_rate_of_change": 0.99,
"refresh_period": 3600
}
]
```

Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting for this pairlist.

!!! Tip
Expand All @@ -660,9 +678,11 @@ Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting f

#### VolatilityFilter

Volatility is the degree of historical variation of a pairs over time, it is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)).
Volatility is the degree of historical variation of a pairs over time, it is measured by the standard deviation of logarithmic candle-to-candle returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)).

This filter removes pairs if the average volatility over a `lookback_days` days is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
This filter removes pairs if the average volatility over `lookback_period` candles of `lookback_timeframe` (defaults to `1d`) is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.

For convenience, `lookback_days` can be used instead, which implies daily candles (equivalent to setting `lookback_period` with a `lookback_timeframe` of `1d`). One of `lookback_days` or `lookback_period` must be set - setting both is ambiguous and will result in an error, as does combining `lookback_days` with a `lookback_timeframe` other than `1d`. Setting neither is deprecated and currently falls back to a lookback of 10 days - this fallback will be removed in a future version.

This filter can be used to narrow down your pairs to a certain volatility or avoid very volatile pairs.

Expand All @@ -673,14 +693,30 @@ If the volatility over the last 10 days is not in the range of 0.05-0.50, remove
"pairlists": [
{
"method": "VolatilityFilter",
"lookback_days": 10,
"lookback_timeframe": "1d",
"lookback_period": 10,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 86400
}
]
```

The same filter based on the volatility of 72 1h candles (3 days) would look as follows:

```json
"pairlists": [
{
"method": "VolatilityFilter",
"lookback_timeframe": "1h",
"lookback_period": 72,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 3600
}
]
```

Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting mode for this pairlist.

### Full example of Pairlist Handlers
Expand Down
2 changes: 1 addition & 1 deletion docs/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Your combined configuration is:
"stake_currency": "USDT",
"exchange": {
"name": "binance",
"key": "REDACTED",
"api_key": "REDACTED",
"secret": "REDACTED",
"ccxt_config": {},
"ccxt_async_config": {},
Expand Down
8 changes: 7 additions & 1 deletion freqtrade/config_schema/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,12 +936,18 @@
"type": "object",
"properties": {
"name": {"description": "Name of the exchange.", "type": "string"},
"api_key": {
"description": (
f"API key for the exchange. {__VIA_ENV} FREQTRADE__EXCHANGE__API_KEY"
),
"type": ["string", "null"],
},
"key": {
"description": (
f"API key for the exchange. {__VIA_ENV} FREQTRADE__EXCHANGE__KEY"
" Deprecated, use api_key instead."
),
"type": ["string", "null"],
"default": None,
},
"secret": {
"description": (
Expand Down
6 changes: 3 additions & 3 deletions freqtrade/configuration/deploy_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ def ask_user_config() -> dict[str, Any]:
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"name": "exchange_api_key",
"message": "Insert Exchange API Key",
"when": lambda x: not x["dry_run"],
},
{
Expand All @@ -149,7 +149,7 @@ def ask_user_config() -> dict[str, Any]:
},
{
"type": "password",
"name": "exchange_key_password",
"name": "exchange_api_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
},
Expand Down
2 changes: 1 addition & 1 deletion freqtrade/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
"dry_run": True,
"exchange": {
"name": "",
"key": None,
"api_key": None,
"secret": None,
"pair_whitelist": [],
"ccxt_async_config": {},
Expand Down
30 changes: 18 additions & 12 deletions freqtrade/exchange/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -2923,37 +2923,43 @@ async def gather_coroutines(coro):
return results_df

def refresh_ohlcv_with_cache(
self, pairs: list[PairWithTimeframe], since_ms: int
self, pairs: list[PairWithTimeframe], *, lookback_period: int
) -> dict[PairWithTimeframe, DataFrame]:
"""
Refresh ohlcv data for all pairs in needed_pairs if necessary.
Caches data with expiring per timeframe.
Should only be used for pairlists which need "on time" expirarion, and no longer cache.
Caches data per (timeframe, lookback_period), expiring with each new candle.
Should only be used for pairlists which need "on time" expiration, and no longer cache.
:param pairs: List of pairs, timeframes to refresh
:param lookback_period: Amount of candles to fetch.
Downloads lookback_period + 1 candles, as measuring a change over N candles
requires N + 1 candles of data.
"""

timeframes = {p[1] for p in pairs}
for timeframe in timeframes:
if (timeframe, since_ms) not in self._expiring_candle_cache:
if (timeframe, lookback_period) not in self._expiring_candle_cache:
timeframe_in_sec = timeframe_to_seconds(timeframe)
# Initialise cache
self._expiring_candle_cache[(timeframe, since_ms)] = PeriodicCache(
self._expiring_candle_cache[(timeframe, lookback_period)] = PeriodicCache(
ttl=timeframe_in_sec, maxsize=1000
)

# Get candles from cache
candles = {
c: self._expiring_candle_cache[(c[1], since_ms)].get(c, None)
c: self._expiring_candle_cache[(c[1], lookback_period)].get(c, None)
for c in pairs
if c in self._expiring_candle_cache[(c[1], since_ms)]
if c in self._expiring_candle_cache[(c[1], lookback_period)]
}
pairs_to_download = [p for p in pairs if p not in candles]
if pairs_to_download:
candles_new = self.refresh_latest_ohlcv(
pairs_to_download, since_ms=since_ms, cache=False
)
for timeframe in timeframes:
tf_pairs = [p for p in pairs_to_download if p[1] == timeframe]
if not tf_pairs:
continue
since_ms = dt_ts(date_minus_candles(timeframe, lookback_period + 1))
candles_new = self.refresh_latest_ohlcv(tf_pairs, since_ms=since_ms, cache=False)
for c, val in candles_new.items():
candles[c] = val
self._expiring_candle_cache[(c[1], since_ms)][c] = val
self._expiring_candle_cache[(c[1], lookback_period)][c] = val
return candles

def _now_is_time_to_refresh(self, pair: str, timeframe: str, candle_type: CandleType) -> bool:
Expand Down
Loading
Loading