From 7521756115956187b2bf06801a1fa471e5ce258b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 30 Nov 2020 09:12:43 +0000 Subject: [PATCH] started refactoring --- bfxbot/__init__.py | 0 bfxbot/bfxbot.py | 87 ++++++++++++++++++++++++++++++++++++++++++++ bfxbot/bfxwrapper.py | 29 +++++++++++++++ bfxbot/event.py | 24 ++++++++++++ bfxbot/status.py | 78 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 bfxbot/__init__.py create mode 100644 bfxbot/bfxbot.py create mode 100644 bfxbot/bfxwrapper.py create mode 100644 bfxbot/event.py create mode 100644 bfxbot/status.py diff --git a/bfxbot/__init__.py b/bfxbot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py new file mode 100644 index 0000000..eaa6dc0 --- /dev/null +++ b/bfxbot/bfxbot.py @@ -0,0 +1,87 @@ +import inspect +from typing import Dict + +from bfxapi import Position + +from bfxbot.bfxwrapper import BfxWrapper +from bfxbot.event import Event, EventKind +from bfxbot.status import Status, PositionState, Ticker + + +class BfxBot: + class EventHandler: + def __init__(self): + self.event_handlers = {} + self.state_handlers = {} + + async def call_event(self, event: Event, status: Status): + value = event.kind.value + if value in self.event_handlers: + for h in self.event_handlers[value]: + if inspect.iscoroutinefunction(h): + await h(event, status) + else: + h(event, status) + + async def call_state(self, state: PositionState, status: Status): + if state in self.state_handlers: + for h in self.state_handlers[state]: + if inspect.iscoroutinefunction(h): + await h(status) + else: + h(status) + + def on_event(self, kind: EventKind): + value = kind.value + + def registerhandler(handler): + if value in self.event_handlers: + self.event_handlers[value].append(handler) + else: + self.event_handlers[value] = [handler] + return handler + + return registerhandler + + def on_state(self, state: PositionState): + def registerhandler(handler): + if state in self.state_handlers: + self.state_handlers[state].append(handler) + else: + self.state_handlers[state] = [handler] + return handler + + return registerhandler + + def __init__(self, api_key: str, api_secret: str, tick_duration: int = 60): + self.bfx: BfxWrapper = BfxWrapper(api_key, api_secret) + self.status: Dict[str, Status] = {} + self.eh: BfxBot.EventHandler = BfxBot.EventHandler() + self.ticker: Ticker = Ticker(tick_duration) + + await self.__update_status__() + + async def __update_status__(self): + active_positions = await self.bfx.get_active_position() + + for p in active_positions: + if p.symbol not in self.status: + self.status[p.symbol] = Status(p.symbol) + + self.status[p.symbol].positions[self.ticker.current_tick].append(p) + + for symbol in self.status.keys(): + active_orders = await self.bfx.get_active_orders(symbol) + + for o in active_orders: + if symbol not in self.status: + self.status[symbol] = Status(symbol) + self.status[symbol].orders[self.ticker.current_tick].append(o) + + def __trigger__events(self): + return + + async def update(self): + self.ticker.inc() + await self.__update_status__() + diff --git a/bfxbot/bfxwrapper.py b/bfxbot/bfxwrapper.py new file mode 100644 index 0000000..39c097d --- /dev/null +++ b/bfxbot/bfxwrapper.py @@ -0,0 +1,29 @@ +from bfxapi.rest.bfx_rest import BfxRest + + +class BfxWrapper(BfxRest): + def __init__(self, api_key: str, api_secret: str): + super().__init__(API_KEY=api_key, API_SECRET=api_secret) + + async def get_current_prices(self, symbol) -> (float, float, float): + tickers = await self.get_public_ticker(symbol) + + bid_price = tickers[0] + ask_price = tickers[2] + ticker_price = tickers[6] + + return bid_price, ask_price, ticker_price + + async def get_usd_balance(self): + balance = 0.0 + + wallets = await self.get_wallets() + + for w in wallets: + if w.currency == "USD": + balance += w.balance + else: + current_price = await self.get_current_prices(f"t{w.currency}USD") + balance += current_price * w.balance + + return balance diff --git a/bfxbot/event.py b/bfxbot/event.py new file mode 100644 index 0000000..b8eb966 --- /dev/null +++ b/bfxbot/event.py @@ -0,0 +1,24 @@ +from enum import Enum + + +class EventKind(Enum): + NEW_MINIMUM = 1, + NEW_MAXIMUM = 2, + REACHED_LOSS = 3, + REACHED_BREAK_EVEN = 4, + REACHED_MIN_PROFIT = 5, + REACHED_GOOD_PROFIT = 6, + REACHED_MAX_LOSS = 7, + CLOSE_POSITION = 8, + TRAILING_STOP_SET = 9, + TRAILING_STOP_MOVED = 10, + ORDER_SUBMITTED = 11, + + +class Event: + def __init__(self, kind: EventKind, tick: int) -> None: + self.kind: EventKind = kind + self.tick: int = tick + + def __repr__(self) -> str: + return f"{self.kind.name} @ Tick {self.tick}" diff --git a/bfxbot/status.py b/bfxbot/status.py new file mode 100644 index 0000000..03ab5c3 --- /dev/null +++ b/bfxbot/status.py @@ -0,0 +1,78 @@ +import time +from enum import Enum +from typing import List, Dict + +from bfxapi import Position, Order + +from bfxbot.bfxbot import BfxBot +from bfxbot.event import Event, EventKind + + +class Ticker: + def __init__(self, sec) -> None: + self.seconds: int = sec + self.start_time = time.time() + self.current_tick: int = 1 + + def inc(self): + self.current_tick += 1 + + +class PositionState(Enum): + CRITICAL = -1, + LOSS = 0, + BREAK_EVEN = 1, + MINIMUM_PROFIT = 2, + PROFIT = 3 + + def color(self) -> str: + if self == self.LOSS or self == self.CRITICAL: + return "red" + elif self == self.BREAK_EVEN: + return "yellow" + else: + return "green" + + +class Status: + def __init__(self, symbol): + self.events: List[Event] = [] + self.symbol = symbol + self.current_state: PositionState = PositionState.LOSS + self.stop_percentage: float = None + self.orders: Dict[int, List[Order]] = {} + self.positions: Dict[int, List[Position]] = {} + + def last_events(self, n) -> List[Event]: + return self.events[-n:] + + def last_position(self) -> Position: + return [self.ticker.current_tick][1] + + async def add_event(self, event: Event, event_handler: BfxBot.EventHandler): + self.events.append(event) + await event_handler.call_event(event, self) + + async def set_state(self, state: PositionState, event_handler: BfxBot.EventHandler, tick: int): + if self.current_state != state: + event: Event = None + + if state == PositionState.CRITICAL: + event = Event(EventKind.REACHED_MAX_LOSS, tick) + elif state == PositionState.LOSS: + event = Event(EventKind.REACHED_LOSS, tick) + elif state == PositionState.BREAK_EVEN: + event = Event(EventKind.REACHED_BREAK_EVEN, tick) + elif state == PositionState.MINIMUM_PROFIT: + event = Event(EventKind.REACHED_MIN_PROFIT, tick) + elif state == PositionState.PROFIT: + event = Event(EventKind.REACHED_GOOD_PROFIT, tick) + + self.events.append(event) + await event_handler.call_event(event, self) + self.current_state = state + + await event_handler.call_state(self.current_state, self) + + def get_current_state(self) -> PositionState: + return self.current_state