implemented close position event and backend algorithm.
This commit is contained in:
parent
f6a318257a
commit
f9f14df519
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
7
main.py
7
main.py
@ -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)
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user