Skip to content

Commit 70a25f9

Browse files
authored
Merge pull request freqtrade#12259 from stash86/delist
Implement delisting check on futures market
2 parents d0546e9 + 4434df1 commit 70a25f9

File tree

14 files changed

+503
-34
lines changed

14 files changed

+503
-34
lines changed

build_helpers/schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@
587587
"RemotePairList",
588588
"MarketCapPairList",
589589
"AgeFilter",
590+
"DelistFilter",
590591
"FullTradesFilter",
591592
"OffsetFilter",
592593
"PerformanceFilter",

config_examples/config_full.example.json

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
"trading_mode": "spot",
2626
"margin_mode": "",
2727
"minimal_roi": {
28-
"40": 0.0,
29-
"30": 0.01,
30-
"20": 0.02,
31-
"0": 0.04
28+
"40": 0.0,
29+
"30": 0.01,
30+
"20": 0.02,
31+
"0": 0.04
3232
},
3333
"stoploss": -0.10,
3434
"unfilledtimeout": {
@@ -47,7 +47,7 @@
4747
"bids_to_ask_delta": 1
4848
}
4949
},
50-
"exit_pricing":{
50+
"exit_pricing": {
5151
"price_side": "same",
5252
"use_order_book": true,
5353
"order_book_top": 1,
@@ -70,18 +70,38 @@
7070
"exit": "GTC"
7171
},
7272
"pairlists": [
73-
{"method": "StaticPairList"},
74-
{"method": "FullTradesFilter"},
73+
{
74+
"method": "StaticPairList"
75+
},
76+
{
77+
"method": "DelistFilter",
78+
"max_days_from_now": 0,
79+
},
80+
{
81+
"method": "FullTradesFilter"
82+
},
7583
{
7684
"method": "VolumePairList",
7785
"number_assets": 20,
7886
"sort_key": "quoteVolume",
7987
"refresh_period": 1800
8088
},
81-
{"method": "AgeFilter", "min_days_listed": 10},
82-
{"method": "PrecisionFilter"},
83-
{"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010},
84-
{"method": "SpreadFilter", "max_spread_ratio": 0.005},
89+
{
90+
"method": "AgeFilter",
91+
"min_days_listed": 10
92+
},
93+
{
94+
"method": "PrecisionFilter"
95+
},
96+
{
97+
"method": "PriceFilter",
98+
"low_price_ratio": 0.01,
99+
"min_price": 0.00000010
100+
},
101+
{
102+
"method": "SpreadFilter",
103+
"max_spread_ratio": 0.005
104+
},
85105
{
86106
"method": "RangeStabilityFilter",
87107
"lookback_days": 10,
@@ -166,12 +186,12 @@
166186
"external_message_consumer": {
167187
"enabled": false,
168188
"producers": [
169-
{
170-
"name": "default",
171-
"host": "127.0.0.2",
172-
"port": 8080,
173-
"ws_token": "secret_ws_t0ken."
174-
}
189+
{
190+
"name": "default",
191+
"host": "127.0.0.2",
192+
"port": 8080,
193+
"ws_token": "secret_ws_t0ken."
194+
}
175195
],
176196
"wait_timeout": 300,
177197
"ping_timeout": 10,
@@ -195,4 +215,4 @@
195215
"reduce_df_footprint": false,
196216
"dataformat_ohlcv": "feather",
197217
"dataformat_trades": "feather"
198-
}
218+
}

docs/includes/pairlists.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade.
44

55
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).
66

7-
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.
7+
Additionally, [`AgeFilter`](#agefilter), [`DelistFilter`](#delistfilter), [`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.
88

99
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.
1010

@@ -27,6 +27,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
2727
* [`RemotePairList`](#remotepairlist)
2828
* [`MarketCapPairList`](#marketcappairlist)
2929
* [`AgeFilter`](#agefilter)
30+
* [`DelistFilter`](#delistfilter)
3031
* [`FullTradesFilter`](#fulltradesfilter)
3132
* [`OffsetFilter`](#offsetfilter)
3233
* [`PerformanceFilter`](#performancefilter)
@@ -180,7 +181,7 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
180181
* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes).
181182
* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day.
182183
* `lookback_timeframe`: Timeframe to use for the lookback period.
183-
* `lookback_period`: Number of periods to look back at.
184+
* `lookback_period`: Number of periods to look back at.
184185

185186
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.
186187

@@ -270,7 +271,6 @@ You can limit the length of the pairlist with the optional parameter `number_ass
270271
],
271272
```
272273

273-
274274
!!! Tip "Combining pairlists"
275275
This pairlist can be combined with all other pairlists and filters for further pairlist reduction, and can also act as an "additional" pairlist, on top of already defined pairs.
276276
`ProducerPairList` can also be used multiple times in sequence, combining the pairs from multiple producers.
@@ -312,7 +312,7 @@ The `pairlist_url` option specifies the URL of the remote server where the pairl
312312
The `save_to_file` option, when provided with a valid filename, saves the processed pairlist to that file in JSON format. This option is optional, and by default, the pairlist is not saved to a file.
313313

314314
??? Example "Multi bot with shared pairlist example"
315-
315+
316316
`save_to_file` can be used to save the pairlist to a file with Bot1:
317317

318318
```json
@@ -407,6 +407,16 @@ be caught out buying before the pair has finished dropping in price.
407407

408408
This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days and listed before `max_days_listed`.
409409

410+
#### DelistFilter
411+
412+
Removes pairs that will be delisted on the exchange maximum `max_days_from_now` days from now (defaults to `0` which remove all future delisted pairs no matter how far from now). Currently this filter only supports following exchanges:
413+
414+
!!! Note "Available exchanges"
415+
Delist filter is only available on Binance, where Binance Futures will work for both dry and live modes, while Binance Spot is limited to live mode (for technical reasons).
416+
417+
!!! Warning "Backtesting"
418+
`DelistFilter` does not support backtesting mode.
419+
410420
#### FullTradesFilter
411421

412422
Shrink whitelist to consist only in-trade pairs when the trade slots are full (when `max_open_trades` isn't being set to `-1` in the config).
@@ -438,7 +448,7 @@ Example to remove the first 10 pairs from the pairlist, and takes the next 20 (t
438448
```
439449

440450
!!! Warning
441-
When `OffsetFilter` is used to split a larger pairlist among multiple bots in combination with `VolumeFilter`
451+
When `OffsetFilter` is used to split a larger pairlist among multiple bots in combination with `VolumeFilter`
442452
it can not be guaranteed that pairs won't overlap due to slightly different refresh intervals for the
443453
`VolumeFilter`.
444454

@@ -601,7 +611,7 @@ Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting m
601611

602612
### Full example of Pairlist Handlers
603613

604-
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#pricefilter), filtering all assets where 1 price unit is > 1%. Then the [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) is applied and pairs are finally shuffled with the random seed set to some predefined value.
614+
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume`, then filter future delisted pairs using [`DelistFilter`](#delistfilter) and [`AgeFilter`](#agefilter) to remove pairs that are listed less than 10 days ago. After that [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#pricefilter) are applied, filtering all assets where 1 price unit is > 1%. Then the [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) are applied and pairs are finally shuffled with the random seed set to some predefined value.
605615

606616
```json
607617
"exchange": {
@@ -614,6 +624,10 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets,
614624
"number_assets": 20,
615625
"sort_key": "quoteVolume"
616626
},
627+
{
628+
"method": "DelistFilter",
629+
"max_days_from_now": 0,
630+
},
617631
{"method": "AgeFilter", "min_days_listed": 10},
618632
{"method": "PrecisionFilter"},
619633
{"method": "PriceFilter", "low_price_ratio": 0.01},

docs/strategy-customization.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Check the [configuration documentation](configuration.md) about how to set the b
8484
**Always use dry mode when testing as this gives you an idea of how your strategy will work in reality without risking capital.**
8585

8686
## Diving in deeper
87+
8788
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py)
8889
file as reference.**
8990

@@ -99,9 +100,9 @@ file as reference.**
99100
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
100101

101102
??? Hint "Lookahead and recursive analysis"
102-
Freqtrade includes two helpful commands to help assess common lookahead (using future data) and
103-
recursive bias (variance in indicator values) issues. Before running a strategy in dry or live more,
104-
you should always use these commands first. Please check the relevant documentation for
103+
Freqtrade includes two helpful commands to help assess common lookahead (using future data) and
104+
recursive bias (variance in indicator values) issues. Before running a strategy in dry or live more,
105+
you should always use these commands first. Please check the relevant documentation for
105106
[lookahead](lookahead-analysis.md) and [recursive](recursive-analysis.md) analysis.
106107

107108
### Dataframe
@@ -154,7 +155,7 @@ Vectorized operations perform calculations across the whole range of data and ar
154155

155156
!!! Warning "Trade order assumptions"
156157
In backtesting, signals are generated on candle close. Trades are then initiated immeditely on next candle open.
157-
158+
158159
In dry and live, this may be delayed due to all pair dataframes needing to be analysed first, then trade processing
159160
for each of those pairs happens. This means that in dry/live you need to be mindful of having as low a computation
160161
delay as possible, usually by running a low number of pairs and having a CPU with a good clock speed.
@@ -284,7 +285,7 @@ It's important to always return the dataframe without removing/modifying the col
284285

285286
This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain `1` for entries, and `0` for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only.
286287

287-
You can name your entry signals by using the `"enter_tag"` column, which can help debug and assess your strategy later.
288+
You can name your entry signals by using the `"enter_tag"` column, which can help debug and assess your strategy later.
288289

289290
Sample from `user_data/strategies/sample_strategy.py`:
290291

@@ -555,7 +556,7 @@ A full sample can be found [in the DataProvider section](#complete-dataprovider-
555556

556557
??? Note "Alternative candle types"
557558
Informative_pairs can also provide a 3rd tuple element defining the candle type explicitly.
558-
Availability of alternative candle-types will depend on the trading-mode and the exchange.
559+
Availability of alternative candle-types will depend on the trading-mode and the exchange.
559560
In general, spot pairs cannot be used in futures markets, and futures candles can't be used as informative pairs for spot bots.
560561
Details about this may vary, if they do, this can be found in the exchange documentation.
561562

@@ -783,6 +784,7 @@ Please always check the mode of operation to select the correct method to get da
783784
- `ohlcv(pair, timeframe)` - Currently cached candle (OHLCV) data for the pair, returns DataFrame or empty DataFrame.
784785
- [`orderbook(pair, maximum)`](#orderbookpair-maximum) - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries.
785786
- [`ticker(pair)`](#tickerpair) - Returns current ticker data for the pair. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#price-tickers) for more details on the Ticker data structure.
787+
- [`check_delisting(pair)`](#check_delistingpair) - Return Datetime of the pair delisting schedule if any, otherwise return None
786788
- [`funding_rate(pair)`](#funding_ratepair) - Returns current funding rate data for the pair.
787789
- `runmode` - Property containing the current runmode.
788790

@@ -906,6 +908,22 @@ if self.dp.runmode.value in ('live', 'dry_run'):
906908
!!! Warning "Warning about backtesting"
907909
This method will always return up-to-date / real-time values. As such, usage during backtesting / hyperopt without runmode checks will lead to wrong results, e.g. your whole dataframe will contain the same single value in all rows.
908910

911+
### *check_delisting(pair)*
912+
913+
```python
914+
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
915+
if self.dp.runmode.value in ('live', 'dry_run'):
916+
delisting_dt = self.dp.check_delisting(pair)
917+
if delisting_dt is not None:
918+
return "delist"
919+
```
920+
921+
!!! Note "Availabiity of delisting information"
922+
This method is only available for certain exchanges and will return `None` in cases this is not available or if the pair is not scheduled for delisting.
923+
924+
!!! Warning "Warning about backtesting"
925+
This method will always return up-to-date / real-time values. As such, usage during backtesting / hyperopt without runmode checks will lead to wrong results, e.g. your whole dataframe will contain the same single value in all rows.
926+
909927
### *funding_rate(pair)*
910928

911929
Retrieves the current funding rate for the pair and only works for futures pairs in the format of `base/quote:settle` (e.g. `ETH/USDT:USDT`).

freqtrade/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"RemotePairList",
5050
"MarketCapPairList",
5151
"AgeFilter",
52+
"DelistFilter",
5253
"FullTradesFilter",
5354
"OffsetFilter",
5455
"PerformanceFilter",

freqtrade/data/dataprovider.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,19 @@ def send_msg(self, message: str, *, always_send: bool = False) -> None:
604604
if always_send or message not in self.__msg_cache:
605605
self._msg_queue.append(message)
606606
self.__msg_cache[message] = True
607+
608+
def check_delisting(self, pair: str) -> datetime | None:
609+
"""
610+
Check if a pair gonna be delisted on the exchange.
611+
Will only return datetime if the pair is gonna be delisted.
612+
:param pair: Pair to check
613+
:return: Datetime of the pair's delisting, None otherwise
614+
"""
615+
if self._exchange is None:
616+
raise OperationalException(NO_EXCHANGE_EXCEPTION)
617+
618+
try:
619+
return self._exchange.check_delisting_time(pair)
620+
except ExchangeError:
621+
logger.warning(f"Could not fetch market data for {pair}. Assuming no delisting.")
622+
return None

0 commit comments

Comments
 (0)