diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py index 5c48be4..c970bdd 100644 --- a/bfxbot/bfxbot.py +++ b/bfxbot/bfxbot.py @@ -65,7 +65,7 @@ class BfxBot: # emitting new tick event ## 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) diff --git a/bfxbot/models.py b/bfxbot/models.py index 659ee77..790dbd6 100644 --- a/bfxbot/models.py +++ b/bfxbot/models.py @@ -120,6 +120,10 @@ class SymbolStatus: self.current_tick: int = 1 self.strategy: Strategy = strategy + async def add_event(self, event: Event): + self.events.append(event) + await self.eh.call_event(self, event) + def add_order(self, order: Order): if self.strategy: self.strategy.order_on_new_tick(order, self) @@ -162,10 +166,6 @@ class SymbolStatus: def set_tick_price(self, tick, price): self.prices[tick] = price - 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, position: Position): if not self.strategy: return @@ -185,7 +185,7 @@ class SymbolStatus: for e in events: if not isinstance(e, Event): raise ValueError - await self.__add_event__(e) + await self.add_event__(e) return pw @@ -239,15 +239,15 @@ class EventHandler: if state in self.state_handlers: for h in self.state_handlers[state]: if inspect.iscoroutinefunction(h): - await h(status) + await h(pw, status) else: - h(status) + h(pw, status) for h in self.any_state: if inspect.iscoroutinefunction(h): - await h(status) + await h(pw, status) else: - h(status) + h(pw, status) def on_event(self, kind: EventKind): value = kind.value diff --git a/main.py b/main.py index 4fc22d5..a7644f7 100755 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ from flask_socketio import SocketIO from bfxbot import BfxBot from bfxbot.currency import Symbol -from bfxbot.models import PositionWrapper, SymbolStatus, Event, EventKind +from bfxbot.models import PositionWrapper, SymbolStatus, Event, EventKind, PositionState from bfxbot.utils import pos_to_json from strategy import TrailingStopStrategy @@ -35,7 +35,8 @@ app = Flask(__name__) socketio = SocketIO(app, async_mode="threading") bot = BfxBot(api_key=API_KEY, api_secret=API_SECRET, symbols=[Symbol.BTC], tick_duration=20) -bot.set_strategy(Symbol.BTC, TrailingStopStrategy()) +strategy = TrailingStopStrategy() +bot.set_strategy(Symbol.BTC, strategy) btc_eh = bot.symbol_event_handler(Symbol.BTC) # initializing and starting bot on other thread @@ -92,6 +93,16 @@ def on_close_position(event: Event, _): loop.run_until_complete(bot.close_position(event.metadata.position_id)) +@btc_eh.on_position_state(PositionState.PROFIT) +async def on_state_min_profit(pw: PositionWrapper, ss: SymbolStatus): + await strategy.update_stop_percentage(pw, ss) + + +@btc_eh.on_position_state(PositionState.MINIMUM_PROFIT) +async def on_state_min_profit(pw: PositionWrapper, ss: SymbolStatus): + await strategy.update_stop_percentage(pw, ss) + + @btc_eh.on_event(EventKind.NEW_TICK) def on_new_tick(event: Event, status: SymbolStatus): tick = event.tick @@ -106,6 +117,7 @@ def on_new_tick(event: Event, status: SymbolStatus): @btc_eh.on_any_event() def on_any_event(event: Event, _): + await strategy socketio.emit("new_event", { "tick": event.tick, "kind": event.kind.name diff --git a/strategy.py b/strategy.py index 01f232f..aa445c6 100644 --- a/strategy.py +++ b/strategy.py @@ -51,6 +51,9 @@ class TrailingStopStrategy(Strategy): TRAILING_STOP = SquaredTrailingStop(Point(MIN_PROFIT_PERC, MIN_PROFIT_PERC / 3 * 2), Point(GOOD_PROFIT_PERC, 0.1)) + def __init__(self): + self.stop_percentage: float = None + def position_on_new_tick(self, position: Position, ss: SymbolStatus) -> (PositionState, List[Event]): events = [] @@ -88,3 +91,21 @@ class TrailingStopStrategy(Strategy): events.append(Event(EventKind.CLOSE_POSITION, ss.current_tick, event_metadata)) return pw, events + + async def update_stop_percentage(self, pw: PositionWrapper, ss: SymbolStatus): + current_pl_perc = pw.net_profit_loss_percentage() + + # set stop percentage for first time + if not self.stop_percentage: + await ss.add_event(Event(EventKind.TRAILING_STOP_SET, ss.current_tick)) + + self.stop_percentage = current_pl_perc - self.TRAILING_STOP.y(current_pl_perc) + + return + + # moving trailing stop + if current_pl_perc - self.TRAILING_STOP.y(pw.net_profit_loss_percentage()) > self.stop_percentage: + await ss.add_event(Event(EventKind.TRAILING_STOP_MOVED, ss.current_tick)) + self.stop_percentage = current_pl_perc - self.TRAILING_STOP.y(current_pl_perc) + + return