AnnouncementsPairList improvements

- create class AnnouncementMixin --> handle multiple exchanges
- refactor BinanceAnnouncementmixin to BinanceAnnouncement and inherit
from AnnouncementMixin
- add Beutifoulsoup in requirements.txt
This commit is contained in:
stefano 2021-10-17 15:30:33 +02:00
parent bedf683322
commit acc66edf22
2 changed files with 77 additions and 23 deletions

View File

@ -7,17 +7,19 @@ Supported exchanges:
- Binance - Binance
""" """
import logging from abc import abstractmethod
import re from bs4 import BeautifulSoup
from cachetools.ttl import TTLCache
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import cached_property
from requests import get
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import logging
import re
import pytz import pytz
from bs4 import BeautifulSoup
from requests import get
import pandas as pd import pandas as pd
from cachetools.ttl import TTLCache
from freqtrade.exceptions import OperationalException, TemporaryError from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -26,11 +28,30 @@ from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# for futures updates def get_token_from_pair(pair: str, index: int = 0) -> str:
SORT_VALUES = ['exchange'] return pair.split('/')[index]
class BinanceAnnouncementMixin: class AnnouncementMixin:
REFRESH_PERIOD: int
TOKEN_COL: str
ANNOUNCEMENT_COL: str
def __init__(self, refresh_period: Optional[int] = None, *args, **kwargs):
# 0 is not allowed, so `or` clause will work everytime
self._refresh_period = refresh_period or self.REFRESH_PERIOD
@abstractmethod
def update_announcements(self, *args, **kwargs) -> pd.DataFrame:
"""
Called by AnnouncementsPairList.
It must be implemented: it must update database with fresh announcements and return updated dataframe.
*args, **kwargs are defined for internal scopes. No arguments will be passed.
"""
class BinanceAnnouncement(AnnouncementMixin):
BINANCE_CATALOG_ID = 48 BINANCE_CATALOG_ID = 48
BINANCE_BASE_URL = "https://www.binance.com/" BINANCE_BASE_URL = "https://www.binance.com/"
BINANCE_ANNOUNCEMENT_URL = BINANCE_BASE_URL + 'en/support/announcement/' BINANCE_ANNOUNCEMENT_URL = BINANCE_BASE_URL + 'en/support/announcement/'
@ -54,15 +75,18 @@ class BinanceAnnouncementMixin:
COLS = ['Token', 'Text', 'Link', 'Datetime discover', 'Datetime announcement'] COLS = ['Token', 'Text', 'Link', 'Datetime discover', 'Datetime announcement']
DB = "BinanceAnnouncements_announcements.csv" DB = "BinanceAnnouncements_announcements.csv"
TOKEN_COL = 'Token'
ANNOUNCEMENT_COL = 'Datetime announcement'
_df: Optional[pd.DataFrame] = None _df: Optional[pd.DataFrame] = None
def update_binance_announcements(self, page_number=1, page_size=10, history=False): def update_announcements(self, page_number=1, page_size=10, history=False) -> pd.DataFrame:
response = None response = None
url = self.get_api_url(page_number, page_size) url = self.get_api_url(page_number, page_size)
if history: if history:
# recursive updating # recursive updating
return [self.update_binance_announcements( return [self.update_announcements(
page, page_size, history=False page, page_size, history=False
) for page in reversed(range(2, 56))][-1] ) for page in reversed(range(2, 56))][-1]
@ -201,17 +225,16 @@ class BinanceAnnouncementMixin:
def announcements_url(self) -> str: def announcements_url(self) -> str:
return self.BINANCE_ANNOUNCEMENTS_URL.format(*[self.BINANCE_CATALOG_ID for _ in range(3)]) return self.BINANCE_ANNOUNCEMENTS_URL.format(*[self.BINANCE_CATALOG_ID for _ in range(3)])
@staticmethod
def get_token_from_pair(pair: str, index: int = 0) -> str:
return pair.split('/')[index]
def get_api_url(self, page_number: int = 1, page_size: int = 10) -> str: def get_api_url(self, page_number: int = 1, page_size: int = 10) -> str:
return self.BINANCE_API_URL.format(self.BINANCE_CATALOG_ID, page_number, page_size) return self.BINANCE_API_URL.format(self.BINANCE_CATALOG_ID, page_number, page_size)
class AnnouncementsPairList(IPairList, BinanceAnnouncementMixin): class AnnouncementsPairList(IPairList):
"""
# sleep at least 3 seconds every request Announcement pair list.
Return pairs that are listed on the selected exchange for less than some specified hours (default 24)
"""
# sleep at least 3 seconds every request by default
REFRESH_PERIOD = 3 REFRESH_PERIOD = 3
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
@ -220,11 +243,23 @@ class AnnouncementsPairList(IPairList, BinanceAnnouncementMixin):
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
if 'exchange' not in self._pairlistconfig:
raise OperationalException(
'`exchange` not specified. Please check your configuration '
'for "pairlist.config.exchange"')
self._stake_currency = config['stake_currency'] self._stake_currency = config['stake_currency']
self._hours = self._pairlistconfig.get('hours', 24) self._hours = self._pairlistconfig.get('hours', 24)
self._pair_exchange = self._pairlistconfig['exchange']
self._refresh_period = self._pairlistconfig.get('refresh_period', self.REFRESH_PERIOD) self._refresh_period = self._pairlistconfig.get('refresh_period', self.REFRESH_PERIOD)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
pair_exchange_kwargs = self._pairlistconfig.get('exchange_kwargs', {})
try:
self.pair_exchange = self._init_pair_exchange(**pair_exchange_kwargs)
except AssertionError as e:
raise OperationalException(f"Announcement class is improperly configured. Exception: {e}") from e
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
@ -238,7 +273,7 @@ class AnnouncementsPairList(IPairList, BinanceAnnouncementMixin):
""" """
Short whitelist method description - used for startup-messages Short whitelist method description - used for startup-messages
""" """
return f"{self.name} - Binance exchange announced pairs." return f"{self.name} - {self._pair_exchange} exchange announced pairs."
def gen_pairlist(self, tickers: Dict) -> List[str]: def gen_pairlist(self, tickers: Dict) -> List[str]:
""" """
@ -270,12 +305,27 @@ class AnnouncementsPairList(IPairList, BinanceAnnouncementMixin):
:param tickers: Tickers (from exchange.get_tickers()). May be cached. :param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: new whitelist :return: new whitelist
""" """
df = self.update_binance_announcements() df = self.pair_exchange.update_announcements()
# TODO migliorare l'efficienza del calcolo # TODO improve performance
pairlist = [ pairlist = [
v for v in pairlist if not df[ v for v in pairlist if not df[
(df['Token'] == self.get_token_from_pair(v)) & (df[self.pair_exchange.TOKEN_COL] == get_token_from_pair(v, 0)) &
(df['Datetime announcement'] > datetime.now().replace(tzinfo=pytz.utc) - timedelta(hours=self._hours)) (df[self.pair_exchange.ANNOUNCEMENT_COL] > (
datetime.now().replace(tzinfo=pytz.utc) - timedelta(hours=self._hours)
))
].empty ].empty
] ]
return pairlist return pairlist
def _init_pair_exchange(self, **pair_exchange_kwargs):
exchange = None
if self._pair_exchange == 'binance':
exchange = BinanceAnnouncement(refresh_period=self._refresh_period, **pair_exchange_kwargs)
if exchange:
assert hasattr(exchange, 'update_announcements'), '`update_announcements` method is required'
assert hasattr(exchange, 'TOKEN_COL'), '`TOKEN_COL` attribute is required'
assert hasattr(exchange, 'ANNOUNCEMENT_COL'), '`ANNOUNCEMENT_COL` attribute is required'
return exchange
raise OperationalException(f'Exchange `{self._pair_exchange}` is not supported yet')

View File

@ -42,3 +42,7 @@ colorama==0.4.4
# Building config files interactively # Building config files interactively
questionary==1.10.0 questionary==1.10.0
prompt-toolkit==3.0.21 prompt-toolkit==3.0.21
# Anncouncements pairlist
beautifulsoup4==4.10.0