state handlers

This commit is contained in:
Giulio De Pasquale 2020-11-28 16:10:27 +00:00
parent fb1433a13d
commit af089554ce

137
main.py
View File

@ -33,14 +33,14 @@ class EventKind(Enum):
REACHED_BREAK_EVEN = 4, REACHED_BREAK_EVEN = 4,
REACHED_MIN_PROFIT = 5, REACHED_MIN_PROFIT = 5,
REACHED_GOOD_PROFIT = 6, REACHED_GOOD_PROFIT = 6,
REACHED_MAX_LOSS = 7 REACHED_MAX_LOSS = 7,
CLOSE_POSITION = 8,
class Event(): class Event():
def __init__(self, kind: EventKind, tick: int, position: Position) -> None: def __init__(self, kind: EventKind, tick: int) -> None:
self.kind: EventKind = kind self.kind: EventKind = kind
self.tick: int = tick self.tick: int = tick
self.position: Position = position
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.kind.name} @ Tick {self.tick}" return f"{self.kind.name} @ Tick {self.tick}"
@ -100,15 +100,16 @@ class Status():
self.ticker: Ticker = Ticker(tick_duration) self.ticker: Ticker = Ticker(tick_duration)
self.events: list[Event] = [] self.events: list[Event] = []
self.symbol = symbol self.symbol = symbol
self.ticks: dict[int, float] = {} self.ticks: dict[int, (float, Position)] = {}
self.current_state: State = State.LOSS self.current_state: State = State.LOSS
self.printer: Printer = printer self.printer: Printer = printer
self.stop_percentage = 999.0 self.stop_percentage: float = None
async def wait(self) -> None: async def update(self, position: Position):
self.ticks[self.get_current_tick()] = (await get_current_price(self.symbol), position)
def wait(self):
sleep(self.ticker.seconds) sleep(self.ticker.seconds)
self.ticks[self.ticker.current_tick] = await get_current_price(self.symbol)
self.ticker.current_tick += 1 self.ticker.current_tick += 1
def get_current_tick(self) -> int: def get_current_tick(self) -> int:
@ -117,6 +118,9 @@ class Status():
def last_events(self, n): def last_events(self, n):
return self.events[-n:] return self.events[-n:]
def last_position(self) -> Position:
return self.ticks[self.ticker.current_tick][1]
def add_event(self, event: Event): def add_event(self, event: Event):
self.events.append(event) self.events.append(event)
@ -125,52 +129,69 @@ class Status():
return await get_current_price(self.symbol) return await get_current_price(self.symbol)
self.ticks[self.get_current_tick()] self.ticks[self.get_current_tick()]
def set_state(self, state: State, position: Position): def set_state(self, state: State):
if self.current_state != state: if self.current_state != state:
event: EventKind = None event: EventKind = None
if state == State.CRITICAL: if state == State.CRITICAL:
event = Event(EventKind.REACHED_MAX_LOSS, event = Event(EventKind.REACHED_MAX_LOSS,
self.get_current_tick(), position) self.get_current_tick())
elif state == State.LOSS: elif state == State.LOSS:
event = Event(EventKind.REACHED_LOSS, event = Event(EventKind.REACHED_LOSS,
self.get_current_tick(), position) self.get_current_tick())
elif state == State.BREAK_EVEN: elif state == State.BREAK_EVEN:
event = Event(EventKind.REACHED_BREAK_EVEN, event = Event(EventKind.REACHED_BREAK_EVEN,
self.get_current_tick(), position) self.get_current_tick())
elif state == State.MINIMUM_PROFIT: elif state == State.MINIMUM_PROFIT:
event = Event(EventKind.REACHED_MIN_PROFIT, event = Event(EventKind.REACHED_MIN_PROFIT,
self.get_current_tick(), position) self.get_current_tick())
elif state == State.PROFIT: elif state == State.PROFIT:
event = Event(EventKind.REACHED_GOOD_PROFIT, event = Event(EventKind.REACHED_GOOD_PROFIT,
self.get_current_tick(), position) self.get_current_tick())
self.events.append(event) self.events.append(event)
eh.call(event, self) eh.call_event(event, self)
self.current_state = state self.current_state = state
eh.call_state(self.current_state, self)
def get_current_state(self) -> State: def get_current_state(self) -> State:
return self.current_state return self.current_state
class EventHandler: class EventHandler:
def __init__(self): def __init__(self):
self.handlers = {} self.event_handlers = {}
self.state_handlers = {}
def call(self, event: Event, status: Status): def call_event(self, event: Event, status: Status):
value = event.kind.value value = event.kind.value
if value in self.handlers: if value in self.event_handlers:
for h in self.handlers[value]: for h in self.event_handlers[value]:
h(event, status) h(event, status)
def call_state(self, state: State, status: Status):
if state in self.state_handlers:
for h in self.state_handlers[state]:
h(status)
def on_event(self, kind: EventKind): def on_event(self, kind: EventKind):
value = kind.value value = kind.value
def registerhandler(handler): def registerhandler(handler):
if value in self.handlers: if value in self.event_handlers:
self.handlers[value].append(handler) self.event_handlers[value].append(handler)
else: else:
self.handlers[value] = [handler] self.event_handlers[value] = [handler]
return handler
return registerhandler
def on_state(self, state: State):
def registerhandler(handler):
if state in self.state_handlers:
self.state_handlers[state].append(handler)
else:
self.state_handlers[state] = [handler]
return handler return handler
return registerhandler return registerhandler
@ -194,8 +215,8 @@ GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 2.1
MAX_LOSS_PERC = -4.0 MAX_LOSS_PERC = -4.0
TRAIL_STOP_PERCENTAGES = { TRAIL_STOP_PERCENTAGES = {
EventKind.REACHED_MIN_PROFIT: 0.2, State.MINIMUM_PROFIT: 0.2,
EventKind.REACHED_GOOD_PROFIT: 0.1 State.PROFIT: 0.1
} }
@ -226,6 +247,49 @@ def on_critical(event: Event, status: Status):
playsound("sounds/gameover.mp3") playsound("sounds/gameover.mp3")
@eh.on_state(State.MINIMUM_PROFIT)
def on_state_min_profit(status: Status):
update_stop_percentage(State.MINIMUM_PROFIT, status)
current_pl_perc = net_pl_percentage(
status.last_position().profit_loss_percentage, TAKER_FEE)
if current_pl_perc < status.stop_percentage:
status.add_event(Event(EventKind.CLOSE_POSITION,
status.get_current_tick()))
@eh.on_state(State.CRITICAL)
def on_state_critical(status: Status):
status.add_event(Event(EventKind.CLOSE_POSITION,
status.get_current_tick()))
@eh.on_state(State.PROFIT)
def on_state_min_profit(status: Status):
update_stop_percentage(State.PROFIT, status)
current_pl_perc = net_pl_percentage(
status.last_position().profit_loss_percentage, TAKER_FEE)
if current_pl_perc < status.stop_percentage:
status.add_event(Event(EventKind.CLOSE_POSITION,
status.get_current_tick()))
def update_stop_percentage(state: State, status: Status):
last_position = status.last_position()
last_pl_net_perc = net_pl_percentage(
last_position.profit_loss_percentage, TAKER_FEE)
# set stop percentage for first time
if not status.stop_percentage or last_pl_net_perc - TRAIL_STOP_PERCENTAGES[state] > status.stop_percentage:
status.stop_percentage = last_pl_net_perc - \
TRAIL_STOP_PERCENTAGES[state]
return
def net_pl_percentage(perc: float, reference_fee_perc: float): def net_pl_percentage(perc: float, reference_fee_perc: float):
return perc - reference_fee_perc return perc - reference_fee_perc
@ -243,13 +307,13 @@ async def main(screen: Screen):
positions = [p for p in await bfx.get_active_position() if p.symbol == status.symbol] positions = [p for p in await bfx.get_active_position() if p.symbol == status.symbol]
orders = await bfx.get_active_orders(symbol) orders = await bfx.get_active_orders(symbol)
last_price = await status.last_price() current_price = await status.last_price()
screen.clear() screen.clear()
printer.print_next_line("Balance: ${} | Current {} price: {} | Current tick ({} sec): {}".format(colored_float(balance, "white"), printer.print_next_line("Balance: ${} | Current {} price: {} | Current tick ({} sec): {}".format(colored_float(balance, "white"),
symbol, symbol,
colored_float( colored_float(
last_price, "white", attrs=["bold"]), current_price, "white", attrs=["bold"]),
status.ticker.seconds, status.ticker.seconds,
status.get_current_tick(), status.get_current_tick(),
)) ))
@ -260,19 +324,21 @@ async def main(screen: Screen):
colored("POSITIONS", attrs=["underline"]))) colored("POSITIONS", attrs=["underline"])))
for p in [p for p in positions if p.symbol == status.symbol]: for p in [p for p in positions if p.symbol == status.symbol]:
await status.update(p)
plfees_percentage = net_pl_percentage( plfees_percentage = net_pl_percentage(
p.profit_loss_percentage, TAKER_FEE) p.profit_loss_percentage, TAKER_FEE)
if plfees_percentage > GOOD_PROFIT_PERC: if plfees_percentage > GOOD_PROFIT_PERC:
status.set_state(State.PROFIT, p) status.set_state(State.PROFIT)
elif plfees_percentage >= MIN_PROFIT_PERC and plfees_percentage < GOOD_PROFIT_PERC: elif plfees_percentage >= MIN_PROFIT_PERC and plfees_percentage < GOOD_PROFIT_PERC:
status.set_state(State.MINIMUM_PROFIT, p) status.set_state(State.MINIMUM_PROFIT)
elif plfees_percentage >= 0.0 and plfees_percentage < MIN_PROFIT_PERC: elif plfees_percentage >= 0.0 and plfees_percentage < MIN_PROFIT_PERC:
status.set_state(State.BREAK_EVEN, p) status.set_state(State.BREAK_EVEN)
elif plfees_percentage < 0.0 and plfees_percentage > MAX_LOSS_PERC: elif plfees_percentage < 0.0 and plfees_percentage > MAX_LOSS_PERC:
status.set_state(State.LOSS, p) status.set_state(State.LOSS)
else: else:
status.set_state(State.CRITICAL, p) status.set_state(State.CRITICAL)
status_color = status.get_current_state().color() status_color = status.get_current_state().color()
@ -282,11 +348,11 @@ async def main(screen: Screen):
if plfees_percentage > max_perc: if plfees_percentage > max_perc:
max_perc = plfees_percentage max_perc = plfees_percentage
status.add_event(Event(EventKind.NEW_MAXIMUM, status.add_event(Event(EventKind.NEW_MAXIMUM,
status.get_current_tick(), p)) status.get_current_tick()))
if plfees_percentage < min_perc: if plfees_percentage < min_perc:
min_perc = plfees_percentage min_perc = plfees_percentage
status.add_event(Event(EventKind.NEW_MINIMUM, status.add_event(Event(EventKind.NEW_MINIMUM,
status.get_current_tick(), p)) status.get_current_tick()))
min_perc_colored = colored_percentage( min_perc_colored = colored_percentage(
min_perc, "red") if min_perc < 0.0 else colored_percentage(min_perc, "green") min_perc, "red") if min_perc < 0.0 else colored_percentage(min_perc, "green")
@ -321,7 +387,7 @@ async def main(screen: Screen):
plot(status, printer) plot(status, printer)
printer.reset_current_line() printer.reset_current_line()
await status.wait() status.wait()
return return
@ -344,8 +410,9 @@ def print_last_events(status: Status, n: int, printer: Printer):
def plot(status: Status, printer: Printer): def plot(status: Status, printer: Printer):
if status.ticks: if status.ticks:
figure = termplotlib.figure() figure = termplotlib.figure()
x = range(1, status.get_current_tick() + 1) x = range(1, status.get_current_tick() + 1)
y = [x for x in status.ticks.values()] y = [x[0] for x in status.ticks.values()]
figure.plot(x, y, width=printer.screen.width, figure.plot(x, y, width=printer.screen.width,
height=printer.screen.height - printer.get_current_line()) height=printer.screen.height - printer.get_current_line())