diff --git a/bfxapi/client.py b/bfxapi/client.py index cefd5ce..e53ca66 100644 --- a/bfxapi/client.py +++ b/bfxapi/client.py @@ -7,5 +7,10 @@ class Constants(str, Enum): PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2" class Client(object): - def __init__(self, WSS_HOST: str = Constants.WSS_HOST, API_KEY: str = None, API_SECRET: str = None): - self.wss = BfxWebsocketClient(host=WSS_HOST, API_KEY=API_KEY, API_SECRET=API_SECRET) \ No newline at end of file + def __init__(self, WSS_HOST: str = Constants.WSS_HOST, API_KEY: str = None, API_SECRET: str = None, log_level: str = "INFO"): + self.wss = BfxWebsocketClient( + host=WSS_HOST, + API_KEY=API_KEY, + API_SECRET=API_SECRET, + log_level=log_level + ) \ No newline at end of file diff --git a/bfxapi/utils/__init__.py b/bfxapi/utils/__init__.py new file mode 100644 index 0000000..5a6afd1 --- /dev/null +++ b/bfxapi/utils/__init__.py @@ -0,0 +1 @@ +NAME = "utils" \ No newline at end of file diff --git a/bfxapi/utils/logger.py b/bfxapi/utils/logger.py new file mode 100644 index 0000000..0ea3894 --- /dev/null +++ b/bfxapi/utils/logger.py @@ -0,0 +1,99 @@ +""" +Module used to describe all of the different data types +""" + +import logging + +RESET_SEQ = "\033[0m" +COLOR_SEQ = "\033[1;%dm" +BOLD_SEQ = "\033[1m" +UNDERLINE_SEQ = "\033[04m" + +YELLOW = '\033[93m' +WHITE = '\33[37m' +BLUE = '\033[34m' +LIGHT_BLUE = '\033[94m' +RED = '\033[91m' +GREY = '\33[90m' + +KEYWORD_COLORS = { + 'WARNING': YELLOW, + 'INFO': LIGHT_BLUE, + 'DEBUG': WHITE, + 'CRITICAL': YELLOW, + 'ERROR': RED, + 'TRADE': '\33[102m\33[30m' +} + +def formatter_message(message, use_color = True): + """ + Syntax highlight certain keywords + """ + if use_color: + message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ) + else: + message = message.replace("$RESET", "").replace("$BOLD", "") + return message + +def format_word(message, word, color_seq, bold=False, underline=False): + """ + Surround the given word with a sequence + """ + replacer = color_seq + word + RESET_SEQ + if underline: + replacer = UNDERLINE_SEQ + replacer + if bold: + replacer = BOLD_SEQ + replacer + return message.replace(word, replacer) + +class Formatter(logging.Formatter): + """ + This Formatted simply colors in the levelname i.e 'INFO', 'DEBUG' + """ + def __init__(self, msg, use_color = True): + logging.Formatter.__init__(self, msg) + self.use_color = use_color + + def format(self, record): + """ + Format and highlight certain keywords + """ + levelname = record.levelname + if self.use_color and levelname in KEYWORD_COLORS: + levelname_color = KEYWORD_COLORS[levelname] + levelname + RESET_SEQ + record.levelname = levelname_color + record.name = GREY + record.name + RESET_SEQ + return logging.Formatter.format(self, record) + +class CustomLogger(logging.Logger): + """ + This adds extra logging functions such as logger.trade and also + sets the logger to use the custom formatter + """ + FORMAT = "[$BOLD%(name)s$RESET] [%(levelname)s] %(message)s" + COLOR_FORMAT = formatter_message(FORMAT, True) + TRADE = 50 + + def __init__(self, name, logLevel='DEBUG'): + logging.Logger.__init__(self, name, logLevel) + color_formatter = Formatter(self.COLOR_FORMAT) + console = logging.StreamHandler() + console.setFormatter(color_formatter) + self.addHandler(console) + logging.addLevelName(self.TRADE, "TRADE") + return + + def set_level(self, level): + logging.Logger.setLevel(self, level) + + def trade(self, message, *args, **kws): + """ + Print a syntax highlighted trade signal + """ + if self.isEnabledFor(self.TRADE): + message = format_word(message, 'CLOSED ', YELLOW, bold=True) + message = format_word(message, 'OPENED ', LIGHT_BLUE, bold=True) + message = format_word(message, 'UPDATED ', BLUE, bold=True) + message = format_word(message, 'CLOSED_ALL ', RED, bold=True) + # Yes, logger takes its '*args' as 'args'. + self._log(self.TRADE, message, args, **kws) \ No newline at end of file diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index e040530..5347b0d 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -8,6 +8,8 @@ from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHand from .exceptions import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion +from ..utils.logger import CustomLogger + HEARTBEAT = "hb" def _require_websocket_connection(function): @@ -115,7 +117,7 @@ class BfxWebsocketClient(object): def on(self, event): if event not in BfxWebsocketClient.EVENTS: - raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events use BfxWebsocketClient.EVENTS.") + raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS") def handler(function): self.event_emitter.on(event, function) @@ -124,7 +126,7 @@ class BfxWebsocketClient(object): def once(self, event): if event not in BfxWebsocketClient.EVENTS: - raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events use BfxWebsocketClient.EVENTS.") + raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS") def handler(function): self.event_emitter.once(event, function)