implemented close position event and backend algorithm.

This commit is contained in:
Giulio De Pasquale 2020-12-16 12:32:16 +00:00
parent f6a318257a
commit f9f14df519
5 changed files with 70 additions and 17 deletions

View File

@ -1,9 +1,11 @@
from time import sleep 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.bfxwrapper import BfxWrapper
from bfxbot.currency import Symbol 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: class BfxBot:
@ -29,6 +31,14 @@ class BfxBot:
for s in self.symbols: for s in self.symbols:
self.__status[s] = SymbolStatus(s) 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): async def __update_status__(self):
active_positions = await self.__bfx.get_active_position() active_positions = await self.__bfx.get_active_position()
@ -57,11 +67,45 @@ class BfxBot:
## TODO: handle _on_new_tick() from Strategy ## TODO: handle _on_new_tick() from Strategy
await self.__status[symbol].__add_event__(Event(EventKind.NEW_TICK, self.__ticker.current_tick)) 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): def close_order(self, symbol: Symbol, order_id: int):
print(f"I would have closed order {order_id} for {symbol}") print(f"I would have closed order {order_id} for {symbol}")
def close_position(self, symbol: Symbol, position_id: int): async def close_position(self, position_id: int):
print(f"I would have closed order {position_id} for {symbol}") 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): def set_strategy(self, symbol, strategy: Strategy):
if symbol in self.__status: if symbol in self.__status:
@ -72,13 +116,13 @@ class BfxBot:
async def start(self): async def start(self):
await self.__update_status__() 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: if symbol not in self.__status:
return None return None
return self.__status[symbol].eh 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: if symbol not in self.__status:
return None return None

View File

@ -1,12 +1,16 @@
import inspect import inspect
import time import time
from enum import Enum from enum import Enum
from typing import List, Dict, Tuple from typing import List, Dict, Tuple, Optional
from bfxapi import Order, Position from bfxapi import Order, Position
from bfxbot.currency import Symbol 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): def __add_to_dict_list__(dict: Dict[int, List], k, v):
if k not in dict: if k not in dict:
@ -59,6 +63,7 @@ class PositionState(Enum):
def __repr__(self): def __repr__(self):
return self.__str__() return self.__str__()
class Ticker: class Ticker:
def __init__(self, sec) -> None: def __init__(self, sec) -> None:
self.seconds: int = sec self.seconds: int = sec
@ -142,12 +147,18 @@ class SymbolStatus:
def current_price(self): def current_price(self):
return self.prices[self.current_tick] 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: if self.current_tick == 1:
return None return None
return next(filter(lambda x: x.position.id == pid, self.positions[self.current_tick - 1])) 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): def set_tick_price(self, tick, price):
self.prices[tick] = price self.prices[tick] = price

View File

@ -1,8 +1,5 @@
from bfxbot.models import PositionWrapper from bfxbot.models import PositionWrapper
TAKER_FEE = 0.2
MAKER_FEE = 0.1
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

View File

@ -24,7 +24,7 @@ async def bot_loop():
await bot.update() await bot.update()
asyncio.new_event_loop() loop = asyncio.new_event_loop()
dotenv.load_dotenv() dotenv.load_dotenv()
@ -58,9 +58,9 @@ def entry():
@socketio.on("close_position") @socketio.on("close_position")
def on_close_position(message: dict): def on_close_position(message: dict):
position_id = message['position_id'] 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') @socketio.on('connect')
def on_connect(): def on_connect():
@ -106,5 +106,6 @@ def on_any_event(event: Event, _):
"kind": event.kind.name "kind": event.kind.name
}) })
if __name__ == '__main__': if __name__ == '__main__':
socketio.run(app, debug=True) socketio.run(app, debug=True)

View File

@ -4,8 +4,9 @@ import sympy.abc
from bfxapi import Position from bfxapi import Position
from sympy import Point, solve from sympy import Point, solve
from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind, EventMetadata, PositionWrapper from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind, EventMetadata, PositionWrapper, \
from bfxbot.utils import TAKER_FEE, net_pl_percentage TAKER_FEE
from bfxbot.utils import net_pl_percentage
class SquaredTrailingStop: class SquaredTrailingStop:
@ -47,7 +48,6 @@ class TrailingStopStrategy(Strategy):
MIN_PROFIT_PERC = BREAK_EVEN_PERC + 0.3 MIN_PROFIT_PERC = BREAK_EVEN_PERC + 0.3
GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 2.5 GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 2.5
MAX_LOSS_PERC = -3.75 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)) TRAILING_STOP = SquaredTrailingStop(Point(MIN_PROFIT_PERC, MIN_PROFIT_PERC / 3 * 2), Point(GOOD_PROFIT_PERC, 0.1))