import inspect import time from enum import Enum from typing import List, Dict from bfxapi import Order, Position from bfxbot.currency import Symbol def __add_to_dict_list__(dict: Dict[int, List], k, v): if k not in dict: dict[k] = [v] return dict[k].append(v) 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 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, NEW_TICK = 12 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 Event: def __init__(self, kind: EventKind, pid: int, tick: int) -> None: self.kind: EventKind = kind self.tick: int = tick # position ID self.pid: int = pid def __repr__(self) -> str: return f"[{self.pid}]: {self.kind.name} @ Tick {self.tick}" class PositionWrapper: def __init__(self, position: Position): self.position: Position = position self.stop_percentage: float = None self.state: PositionState = PositionState.LOSS def get_stop_percentage(self) -> float: return self.stop_percentage def get_state(self) -> PositionState: return self.state def set_stop_percentage(self, perc: float): self.stop_percentage = perc def set_state(self, state: PositionState): if not isinstance(state, PositionState): return self.state = state class SymbolStatus: def __init__(self, symbol: Symbol, strategy=None): self.symbol = symbol self.eh = EventHandler() self.prices: Dict[int, float] = {} self.events: List[Event] = [] self.orders: Dict[int, List[Order]] = {} self.positions: Dict[int, List[PositionWrapper]] = {} self.current_tick: int = 1 self.strategy: Strategy = strategy def last_events(self, n) -> List[Event]: return self.events[-n:] def last_positions(self) -> List[PositionWrapper]: return self.positions[self.current_tick] # Applies strategy and adds position to list async def add_position(self, position: Position): pw = PositionWrapper(position) if self.strategy: await self.__apply_strategy_to_position__(pw) __add_to_dict_list__(self.positions, self.current_tick, pw) def add_order(self, order: Order): if self.strategy: self.strategy.order_on_tick(order, self) __add_to_dict_list__(self.orders, self.current_tick, order) def previous_position_w(self, pid: int) -> PositionWrapper: if self.current_tick == 1: return None return next(filter(lambda x: x.pid == pid, self.positions[self.current_tick - 1])) async def __add_event__(self, event: Event): self.events.append(event) await self.eh.call_event(self, event) async def __apply_strategy_to_position__(self, pw: PositionWrapper): (new_state, events) = self.strategy.position_on_tick(pw.position, self) if isinstance(new_state, PositionState): await self.__update_position_state__(pw, new_state) if isinstance(events, List): for e in events: if isinstance(e, Event): await self.__add_event__(e) async def __update_position_state__(self, pw: PositionWrapper, state: PositionState): pw.set_state(state) await self.eh.call_state(self, pw) def set_price(self, tick, price): self.prices[tick] = price class Strategy: """ Defines new position state and events after tick. """ def position_on_tick(self, position: Position, ss: SymbolStatus) -> (PositionState, List[Event]): pass """ Defines new order state and events after tick. """ def order_on_tick(self, order: Order, ss: SymbolStatus): pass class EventHandler: def __init__(self): self.event_handlers = {} self.state_handlers = {} self.any_events = [] self.any_state = [] async def call_event(self, status: SymbolStatus, event: Event): value = event.kind.value # print("CALLING EVENT: {}".format(event)) 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) for h in self.any_events: if inspect.iscoroutinefunction(h): await h(event, status) else: h(event, status) async def call_state(self, status: SymbolStatus, pw: PositionWrapper): state = pw.state if state in self.state_handlers: for h in self.state_handlers[state]: if inspect.iscoroutinefunction(h): await h(status) else: h(status) for h in self.any_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 on_any_event(self): def registerhandle(handler): self.any_events.append(handler) return handler return registerhandle def on_any_state(self): def registerhandle(handler): self.any_state.append(handler) return handler return registerhandle