core/bfxbot/bfxbot.py
2020-12-16 12:32:16 +00:00

135 lines
4.4 KiB
Python

from time import sleep
from typing import Dict, List, Optional, Tuple
from bfxapi import Order
from bfxbot.bfxwrapper import BfxWrapper
from bfxbot.currency import Symbol
from bfxbot.models import SymbolStatus, Ticker, EventHandler, Strategy, Event, EventKind, OFFER_PERC, PositionWrapper
class BfxBot:
def __init__(self, api_key: str, api_secret: str, symbols: List[Symbol], tick_duration: int = 1, ):
if api_key is None:
print("API_KEY is not set!")
raise ValueError
if api_secret is None:
print("API_SECRET is not set!")
raise ValueError
self.__bfx: BfxWrapper = BfxWrapper(api_key, api_secret)
self.__ticker: Ticker = Ticker(tick_duration)
self.__status: Dict[Symbol, SymbolStatus] = {}
if isinstance(symbols, Symbol):
symbols = [symbols]
self.symbols: List[Symbol] = symbols
# init symbol statuses
for s in self.symbols:
self.__status[s] = SymbolStatus(s)
def __position_wrapper_from_id(self, position_id) -> Optional[Tuple[PositionWrapper, SymbolStatus]]:
for s in self.__status.values():
pw = s.active_position_wrapper_from_id(position_id)
if pw:
return pw, s
return None
async def __update_status__(self):
active_positions = await self.__bfx.get_active_position()
for symbol in self.__status:
# updating tick
self.__status[symbol].current_tick = self.__ticker.current_tick
# updating last price
last_price = await self.__bfx.get_current_prices(symbol)
last_price = last_price[0]
self.__status[symbol].set_tick_price(self.__ticker.current_tick, last_price)
# updating positions
symbol_positions = [x for x in active_positions if x.symbol == str(symbol)]
for p in symbol_positions:
await self.__status[Symbol.from_str(p.symbol)].add_position(p)
# updating orders
active_orders = await self.__bfx.get_active_orders(str(symbol))
for o in active_orders:
self.__status[symbol].add_order(o)
# emitting new tick event
## TODO: handle _on_new_tick() from Strategy
await self.__status[symbol].__add_event__(Event(EventKind.NEW_TICK, self.__ticker.current_tick))
async def best_position_closing_price(self, position_id: int) -> Optional[float]:
pw: Optional[PositionWrapper] = self.__position_wrapper_from_id(position_id)
if not pw:
return None
is_long_pos = pw.position.amount < 0
pub_tick = await self.__bfx.get_public_ticker(pw.position.symbol)
bid_price = pub_tick[0]
ask_price = pub_tick[2]
if is_long_pos:
closing_price = bid_price * (1 - OFFER_PERC / 100)
else:
closing_price = ask_price * (1 + OFFER_PERC / 100)
return closing_price
def close_order(self, symbol: Symbol, order_id: int):
print(f"I would have closed order {order_id} for {symbol}")
async def close_position(self, position_id: int):
pw, ss = self.__position_wrapper_from_id(position_id)
if not pw:
print("Could not find open position!")
return
closing_price = await self.best_position_closing_price(pw.position.id)
amount = pw.position.amount * -1
open_orders = await self.__bfx.get_active_orders(pw.position.symbol)
if not open_orders:
await self.__bfx.submit_order(pw.position.symbol, closing_price, amount, Order.Type.LIMIT)
await ss.__add_event(Event(EventKind.ORDER_SUBMITTED, ss.current_tick))
def set_strategy(self, symbol, strategy: Strategy):
if symbol in self.__status:
self.__status[symbol].strategy = strategy
else:
self.__status[symbol] = SymbolStatus(symbol, strategy)
async def start(self):
await self.__update_status__()
def symbol_event_handler(self, symbol) -> Optional[EventHandler]:
if symbol not in self.__status:
return None
return self.__status[symbol].eh
def symbol_status(self, symbol: Symbol) -> Optional[SymbolStatus]:
if symbol not in self.__status:
return None
return self.__status[symbol]
async def update(self):
sleep(self.__ticker.seconds)
self.__ticker.inc()
await self.__update_status__()