From f9f14df519157b6cf4158e0c8609a48b0e415fda Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 16 Dec 2020 12:32:16 +0000 Subject: [PATCH] implemented close position event and backend algorithm. --- bfxbot/bfxbot.py | 56 ++++++++++++++++++++++++++++++++++++++++++------ bfxbot/models.py | 15 +++++++++++-- bfxbot/utils.py | 3 --- main.py | 7 +++--- strategy.py | 6 +++--- 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py index 7a1571b..5c48be4 100644 --- a/bfxbot/bfxbot.py +++ b/bfxbot/bfxbot.py @@ -1,9 +1,11 @@ from time import sleep -from typing import Dict, List +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 +from bfxbot.models import SymbolStatus, Ticker, EventHandler, Strategy, Event, EventKind, OFFER_PERC, PositionWrapper class BfxBot: @@ -29,6 +31,14 @@ class BfxBot: 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() @@ -57,11 +67,45 @@ class BfxBot: ## 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}") - def close_position(self, symbol: Symbol, position_id: int): - print(f"I would have closed order {position_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: @@ -72,13 +116,13 @@ class BfxBot: async def start(self): await self.__update_status__() - def symbol_event_handler(self, symbol) -> EventHandler: + 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) -> SymbolStatus: + def symbol_status(self, symbol: Symbol) -> Optional[SymbolStatus]: if symbol not in self.__status: return None diff --git a/bfxbot/models.py b/bfxbot/models.py index 8cfd4ce..659ee77 100644 --- a/bfxbot/models.py +++ b/bfxbot/models.py @@ -1,12 +1,16 @@ import inspect import time from enum import Enum -from typing import List, Dict, Tuple +from typing import List, Dict, Tuple, Optional from bfxapi import Order, Position from bfxbot.currency import Symbol +OFFER_PERC = 0.008 +TAKER_FEE = 0.2 +MAKER_FEE = 0.1 + def __add_to_dict_list__(dict: Dict[int, List], k, v): if k not in dict: @@ -59,6 +63,7 @@ class PositionState(Enum): def __repr__(self): return self.__str__() + class Ticker: def __init__(self, sec) -> None: self.seconds: int = sec @@ -142,12 +147,18 @@ class SymbolStatus: def current_price(self): return self.prices[self.current_tick] - def previous_pw(self, pid: int) -> PositionWrapper: + def previous_pw(self, pid: int) -> Optional[PositionWrapper]: if self.current_tick == 1: return None return next(filter(lambda x: x.position.id == pid, self.positions[self.current_tick - 1])) + def active_position_wrapper_from_id(self, position_id: int) -> Optional[PositionWrapper]: + for pw in self.positions[self.current_tick]: + if pw.position.id == position_id: + return pw + return None + def set_tick_price(self, tick, price): self.prices[tick] = price diff --git a/bfxbot/utils.py b/bfxbot/utils.py index a25cb9b..0e5c79a 100644 --- a/bfxbot/utils.py +++ b/bfxbot/utils.py @@ -1,8 +1,5 @@ from bfxbot.models import PositionWrapper -TAKER_FEE = 0.2 -MAKER_FEE = 0.1 - def net_pl_percentage(perc: float, reference_fee_perc: float): return perc - reference_fee_perc diff --git a/main.py b/main.py index 71f546f..c77f8ab 100755 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ async def bot_loop(): await bot.update() -asyncio.new_event_loop() +loop = asyncio.new_event_loop() dotenv.load_dotenv() @@ -58,9 +58,9 @@ def entry(): @socketio.on("close_position") def on_close_position(message: dict): position_id = message['position_id'] - symbol= Symbol.from_str(message['symbol']) - bot.close_position(symbol, position_id) + loop.run_until_complete(bot.close_position(position_id)) + @socketio.on('connect') def on_connect(): @@ -106,5 +106,6 @@ def on_any_event(event: Event, _): "kind": event.kind.name }) + if __name__ == '__main__': socketio.run(app, debug=True) diff --git a/strategy.py b/strategy.py index deb7339..01f232f 100644 --- a/strategy.py +++ b/strategy.py @@ -4,8 +4,9 @@ import sympy.abc from bfxapi import Position from sympy import Point, solve -from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind, EventMetadata, PositionWrapper -from bfxbot.utils import TAKER_FEE, net_pl_percentage +from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind, EventMetadata, PositionWrapper, \ + TAKER_FEE +from bfxbot.utils import net_pl_percentage class SquaredTrailingStop: @@ -47,7 +48,6 @@ class TrailingStopStrategy(Strategy): MIN_PROFIT_PERC = BREAK_EVEN_PERC + 0.3 GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 2.5 MAX_LOSS_PERC = -3.75 - OFFER_PERC = 0.005 TRAILING_STOP = SquaredTrailingStop(Point(MIN_PROFIT_PERC, MIN_PROFIT_PERC / 3 * 2), Point(GOOD_PROFIT_PERC, 0.1))