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:
parent
bedf683322
commit
acc66edf22
@ -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]
|
||||||
|
|
||||||
@ -80,7 +104,7 @@ class BinanceAnnouncementMixin:
|
|||||||
f"Status code: {response.status_code}\n"
|
f"Status code: {response.status_code}\n"
|
||||||
f"Content: {response.content.decode()}")
|
f"Content: {response.content.decode()}")
|
||||||
|
|
||||||
logger.info("Updating from Binance ...")
|
logger.info("Updating from Binance...")
|
||||||
updated_list = []
|
updated_list = []
|
||||||
|
|
||||||
for article in response.json()['data']['articles']:
|
for article in response.json()['data']['articles']:
|
||||||
@ -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')
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user