Merge pull request #2830 from orehunt/spreadfilter
- added spread filter
This commit is contained in:
commit
b5ee4f17cb
@ -62,8 +62,8 @@
|
|||||||
"refresh_period": 1800
|
"refresh_period": 1800
|
||||||
},
|
},
|
||||||
{"method": "PrecisionFilter"},
|
{"method": "PrecisionFilter"},
|
||||||
{"method": "PriceFilter", "low_price_ratio": 0.01
|
{"method": "PriceFilter", "low_price_ratio": 0.01},
|
||||||
}
|
{"method": "SpreadFilter", "max_spread_ratio": 0.005}
|
||||||
],
|
],
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "bittrex",
|
||||||
|
@ -503,6 +503,7 @@ Inactive markets and blacklisted pairs are always removed from the resulting `pa
|
|||||||
* [`VolumePairList`](#volume-pair-list)
|
* [`VolumePairList`](#volume-pair-list)
|
||||||
* [`PrecisionFilter`](#precision-filter)
|
* [`PrecisionFilter`](#precision-filter)
|
||||||
* [`PriceFilter`](#price-pair-filter)
|
* [`PriceFilter`](#price-pair-filter)
|
||||||
|
* [`SpreadFilter`](#spread-filter)
|
||||||
|
|
||||||
!!! Tip "Testing pairlists"
|
!!! Tip "Testing pairlists"
|
||||||
Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) subcommand to test your configuration quickly.
|
Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) subcommand to test your configuration quickly.
|
||||||
@ -551,6 +552,11 @@ Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.
|
|||||||
|
|
||||||
These pairs are dangerous since it may be impossible to place the desired stoploss - and often result in high losses.
|
These pairs are dangerous since it may be impossible to place the desired stoploss - and often result in high losses.
|
||||||
|
|
||||||
|
#### Spread Filter
|
||||||
|
Removes pairs that have a difference between asks and bids above the specified ratio (default `0.005`).
|
||||||
|
Example:
|
||||||
|
If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027 the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005`
|
||||||
|
|
||||||
### Full Pairlist example
|
### Full Pairlist example
|
||||||
|
|
||||||
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`PriceFilter`](#price-pair-filter), filtering all assets where 1 priceunit is > 1%.
|
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`PriceFilter`](#price-pair-filter), filtering all assets where 1 priceunit is > 1%.
|
||||||
|
@ -17,7 +17,8 @@ REQUIRED_ORDERTIF = ['buy', 'sell']
|
|||||||
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||||
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter']
|
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
||||||
|
'PrecisionFilter', 'PriceFilter', 'SpreadFilter']
|
||||||
DRY_RUN_WALLET = 1000
|
DRY_RUN_WALLET = 1000
|
||||||
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
||||||
|
|
||||||
|
59
freqtrade/pairlist/SpreadFilter.py
Normal file
59
freqtrade/pairlist/SpreadFilter.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SpreadFilter(IPairList):
|
||||||
|
|
||||||
|
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict,
|
||||||
|
pairlist_pos: int) -> None:
|
||||||
|
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
||||||
|
|
||||||
|
self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def needstickers(self) -> bool:
|
||||||
|
"""
|
||||||
|
Boolean property defining if tickers are necessary.
|
||||||
|
If no Pairlist requries tickers, an empty List is passed
|
||||||
|
as tickers argument to filter_pairlist
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def short_desc(self) -> str:
|
||||||
|
"""
|
||||||
|
Short whitelist method description - used for startup-messages
|
||||||
|
"""
|
||||||
|
return (f"{self.name} - Filtering pairs with ask/bid diff above "
|
||||||
|
f"{self._max_spread_ratio * 100}%.")
|
||||||
|
|
||||||
|
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Filters and sorts pairlist and returns the whitelist again.
|
||||||
|
Called on each bot iteration - please use internal caching if necessary
|
||||||
|
:param pairlist: pairlist to filter or sort
|
||||||
|
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
||||||
|
:return: new whitelist
|
||||||
|
"""
|
||||||
|
# Copy list since we're modifying this list
|
||||||
|
|
||||||
|
spread = None
|
||||||
|
for p in deepcopy(pairlist):
|
||||||
|
ticker = tickers.get(p)
|
||||||
|
assert ticker is not None
|
||||||
|
if 'bid' in ticker and 'ask' in ticker:
|
||||||
|
spread = 1 - ticker['bid'] / ticker['ask']
|
||||||
|
if not ticker or spread > self._max_spread_ratio:
|
||||||
|
logger.info(f"Removed {ticker['symbol']} from whitelist, "
|
||||||
|
f"because spread {spread * 100:.3f}% >"
|
||||||
|
f"{self._max_spread_ratio * 100}%")
|
||||||
|
pairlist.remove(p)
|
||||||
|
else:
|
||||||
|
pairlist.remove(p)
|
||||||
|
|
||||||
|
return pairlist
|
@ -28,6 +28,7 @@ class VolumePairList(IPairList):
|
|||||||
'for "pairlist.config.number_assets"')
|
'for "pairlist.config.number_assets"')
|
||||||
self._number_pairs = self._pairlistconfig['number_assets']
|
self._number_pairs = self._pairlistconfig['number_assets']
|
||||||
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
|
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
|
||||||
|
self._min_value = self._pairlistconfig.get('min_value', 0)
|
||||||
self.refresh_period = self._pairlistconfig.get('refresh_period', 1800)
|
self.refresh_period = self._pairlistconfig.get('refresh_period', 1800)
|
||||||
|
|
||||||
if not self._exchange.exchange_has('fetchTickers'):
|
if not self._exchange.exchange_has('fetchTickers'):
|
||||||
@ -73,12 +74,13 @@ class VolumePairList(IPairList):
|
|||||||
tickers,
|
tickers,
|
||||||
self._config['stake_currency'],
|
self._config['stake_currency'],
|
||||||
self._sort_key,
|
self._sort_key,
|
||||||
|
self._min_value
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return pairlist
|
return pairlist
|
||||||
|
|
||||||
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict,
|
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict,
|
||||||
base_currency: str, key: str) -> List[str]:
|
base_currency: str, key: str, min_val: int) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Updates the whitelist with with a dynamically generated list
|
Updates the whitelist with with a dynamically generated list
|
||||||
:param base_currency: base currency as str
|
:param base_currency: base currency as str
|
||||||
@ -97,6 +99,9 @@ class VolumePairList(IPairList):
|
|||||||
# If other pairlist is in front, use the incomming pairlist.
|
# If other pairlist is in front, use the incomming pairlist.
|
||||||
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
||||||
|
|
||||||
|
if min_val > 0:
|
||||||
|
filtered_tickers = list(filter(lambda t: t[key] > min_val, filtered_tickers))
|
||||||
|
|
||||||
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key])
|
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key])
|
||||||
|
|
||||||
# Validate whitelist to only have active market pairs
|
# Validate whitelist to only have active market pairs
|
||||||
|
@ -640,6 +640,31 @@ def shitcoinmarkets(markets):
|
|||||||
},
|
},
|
||||||
'info': {},
|
'info': {},
|
||||||
},
|
},
|
||||||
|
'NANO/USDT': {
|
||||||
|
"percentage": True,
|
||||||
|
"tierBased": False,
|
||||||
|
"taker": 0.001,
|
||||||
|
"maker": 0.001,
|
||||||
|
"precision": {
|
||||||
|
"base": 8,
|
||||||
|
"quote": 8,
|
||||||
|
"amount": 2,
|
||||||
|
"price": 4
|
||||||
|
},
|
||||||
|
"limits": {
|
||||||
|
},
|
||||||
|
"id": "NANOUSDT",
|
||||||
|
"symbol": "NANO/USDT",
|
||||||
|
"base": "NANO",
|
||||||
|
"quote": "USDT",
|
||||||
|
"baseId": "NANO",
|
||||||
|
"quoteId": "USDT",
|
||||||
|
"info": {},
|
||||||
|
"type": "spot",
|
||||||
|
"spot": True,
|
||||||
|
"future": False,
|
||||||
|
"active": True
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return shitmarkets
|
return shitmarkets
|
||||||
|
|
||||||
@ -1114,6 +1139,28 @@ def tickers():
|
|||||||
'quoteVolume': 1154.19266394,
|
'quoteVolume': 1154.19266394,
|
||||||
'info': {}
|
'info': {}
|
||||||
},
|
},
|
||||||
|
"NANO/USDT": {
|
||||||
|
"symbol": "NANO/USDT",
|
||||||
|
"timestamp": 1580469388244,
|
||||||
|
"datetime": "2020-01-31T11:16:28.244Z",
|
||||||
|
"high": 0.7519,
|
||||||
|
"low": 0.7154,
|
||||||
|
"bid": 0.7305,
|
||||||
|
"bidVolume": 300.3,
|
||||||
|
"ask": 0.7342,
|
||||||
|
"askVolume": 15.14,
|
||||||
|
"vwap": 0.73645591,
|
||||||
|
"open": 0.7154,
|
||||||
|
"close": 0.7342,
|
||||||
|
"last": 0.7342,
|
||||||
|
"previousClose": 0.7189,
|
||||||
|
"change": 0.0188,
|
||||||
|
"percentage": 2.628,
|
||||||
|
"average": None,
|
||||||
|
"baseVolume": 439472.44,
|
||||||
|
"quoteVolume": 323652.075405,
|
||||||
|
"info": {}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
|||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}],
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}],
|
||||||
"BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
|
"BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
|
||||||
"USDT", ['ETH/USDT']),
|
"USDT", ['ETH/USDT', 'NANO/USDT']),
|
||||||
# No pair for ETH ...
|
# No pair for ETH ...
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
|
||||||
"ETH", []),
|
"ETH", []),
|
||||||
@ -160,6 +160,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
|||||||
{"method": "PrecisionFilter"},
|
{"method": "PrecisionFilter"},
|
||||||
{"method": "PriceFilter", "low_price_ratio": 0.02}
|
{"method": "PriceFilter", "low_price_ratio": 0.02}
|
||||||
], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
|
], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
|
||||||
|
# HOT and XRP are removed because below 1250 quoteVolume
|
||||||
|
([{"method": "VolumePairList", "number_assets": 5,
|
||||||
|
"sort_key": "quoteVolume", "min_value": 1250}],
|
||||||
|
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
|
||||||
# StaticPairlist Only
|
# StaticPairlist Only
|
||||||
([{"method": "StaticPairList"},
|
([{"method": "StaticPairList"},
|
||||||
], "BTC", ['ETH/BTC', 'TKN/BTC']),
|
], "BTC", ['ETH/BTC', 'TKN/BTC']),
|
||||||
@ -167,6 +171,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
|||||||
([{"method": "StaticPairList"},
|
([{"method": "StaticPairList"},
|
||||||
{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"},
|
{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"},
|
||||||
], "BTC", ['TKN/BTC', 'ETH/BTC']),
|
], "BTC", ['TKN/BTC', 'ETH/BTC']),
|
||||||
|
# SpreadFilter
|
||||||
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
|
||||||
|
{"method": "SpreadFilter", "max_spread": 0.005}
|
||||||
|
], "USDT", ['ETH/USDT']),
|
||||||
])
|
])
|
||||||
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
|
||||||
pairlists, base_currency, whitelist_result,
|
pairlists, base_currency, whitelist_result,
|
||||||
|
Loading…
Reference in New Issue
Block a user