From 6c7f0989a673e77d48f0df150ba0001a2ab17678 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 7 Dec 2020 12:27:09 +0000 Subject: [PATCH 1/3] moved trailing stop strategy to separate file. added function to calculate trailing stop --- main.py | 84 ++++++++--------------------------------------- strategy.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 strategy.py diff --git a/main.py b/main.py index 4f707fa..2a135f0 100755 --- a/main.py +++ b/main.py @@ -485,92 +485,34 @@ # if __name__ == "__main__": # asyncio.run(Screen.wrapper(main)) import asyncio -from typing import List +import os -from bfxapi import Position +import dotenv from bfxbot import BfxBot -import dotenv -import os -import bfxapi - from bfxbot.currency import Symbol -from bfxbot.models import Strategy, SymbolStatus, PositionState, Event, EventKind -from bfxbot.utils import TAKER_FEE, net_pl_percentage +from bfxbot.models import PositionState +from strategy import TrailingStopStrategy dotenv.load_dotenv() +API_KEY = os.getenv("API_KEY") +API_SECRET = os.getenv("API_SECRET") -class TrailingStopStrategy(Strategy): - BREAK_EVEN_PERC = TAKER_FEE - MIN_PROFIT_PERC = TAKER_FEE * 2.5 - GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 1.5 - MAX_LOSS_PERC = -3.75 - OFFER_PERC = 0.01 +bot = BfxBot(api_key=API_KEY, api_secret=API_SECRET) +strategy = TrailingStopStrategy() +bot.set_strategy(Symbol.BTC, strategy) +eh = bot.event_handler(Symbol.BTC) - TRAIL_STOP_PERCENTAGES = { - PositionState.MINIMUM_PROFIT: 0.27, - PositionState.PROFIT: 0.14 - } - def position_on_tick(self, position: Position, ss: SymbolStatus) -> (PositionState, List[Event]): - events = [] - - pl_perc = net_pl_percentage(position.profit_loss_percentage, TAKER_FEE) - prev = ss.previous_position_w(position.id) - - if pl_perc > self.GOOD_PROFIT_PERC: - state = PositionState.PROFIT - elif self.MIN_PROFIT_PERC <= pl_perc < self.GOOD_PROFIT_PERC: - state = PositionState.MINIMUM_PROFIT - elif 0.0 <= pl_perc < self.MIN_PROFIT_PERC: - state = PositionState.BREAK_EVEN - elif self.MAX_LOSS_PERC < pl_perc < 0.0: - state = PositionState.LOSS - else: - state = PositionState.CRITICAL - - if not prev or prev.state == state: - return state, events - - if state ==PositionState.PROFIT: - events.append(Event(EventKind.REACHED_GOOD_PROFIT, position.id, ss.current_tick)) - elif state == PositionState.MINIMUM_PROFIT: - events.append(Event(EventKind.REACHED_MIN_PROFIT, position.id, ss.current_tick)) - elif state == PositionState.BREAK_EVEN: - events.append(Event(EventKind.REACHED_BREAK_EVEN, position.id, ss.current_tick)) - elif state== PositionState.LOSS: - events.append(Event(EventKind.REACHED_LOSS, position.id, ss.current_tick)) - else: - events.append(Event(EventKind.REACHED_MAX_LOSS, position.id, ss.current_tick)) - events.append(Event(EventKind.CLOSE_POSITION, position.id, ss.current_tick)) - - return state, events +@eh.on_state(PositionState.PROFIT) +def on_min_profit(ss, pw): + print("Minimum profit!") async def main(): - API_KEY = os.getenv("API_KEY") - API_SECRET = os.getenv("API_SECRET") - - if API_KEY == None: - print("API_KEY is not set! Set the var in the .env file.") - return - - if API_SECRET == None: - print("API_SECRET is not set! Set the var in the .env file.") - return - - bot = BfxBot(api_key=API_KEY, api_secret=API_SECRET) - strategy = TrailingStopStrategy() - bot.set_strategy(Symbol.BTC, strategy) - await bot.start() - eh = bot.event_handler(Symbol.BTC) - while True: - print("WAITING...") await bot.update() - - if __name__ == '__main__': asyncio.run(main()) \ No newline at end of file diff --git a/strategy.py b/strategy.py new file mode 100644 index 0000000..32dce20 --- /dev/null +++ b/strategy.py @@ -0,0 +1,93 @@ +from typing import List + +import sympy.abc +from sympy import Point, solve + +from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind +from bfxbot.utils import TAKER_FEE, net_pl_percentage + + +class Position(object): + def __init__(self): + self.id = None + self.profit_loss_percentage = None + + pass + +class SquaredTrailingStop: + def __init__(self, p_min: Point, p_max: Point): + a = sympy.abc.a + b = sympy.abc.b + c = sympy.abc.c + + self.p_min = p_min + self.p_max = p_max + + e1 = 2 * a * (p_max.x + b) + e2 = a * (p_min.x + b)**2 + c - p_min.y + e3 = a * (p_max.x + b)**2 + c - p_max.y + + s = solve([e1, e2, e3])[0] + + self.a, self.b, self.c = s[a], s[b], s[c] + + def y(self, x): + def inter_y(x): + return self.a * (x + self.b)**2 + self.c + + if x < self.p_min.x: + return self.p_min.y + elif x > self.p_max.x: + return self.p_max.y + else: + return inter_y(x) + + def profit(self, x): + if x < self.p_min.x: + return 0 + return x - self.y(x) + +class TrailingStopStrategy(Strategy): + BREAK_EVEN_PERC = TAKER_FEE + 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)) + + def position_on_tick(self, position: Position, ss: SymbolStatus) -> (PositionState, List[Event]): + events = [] + + pl_perc = net_pl_percentage(position.profit_loss_percentage, TAKER_FEE) + prev = ss.previous_position_w(position.id) + + if pl_perc > self.GOOD_PROFIT_PERC: + state = PositionState.PROFIT + elif self.MIN_PROFIT_PERC <= pl_perc < self.GOOD_PROFIT_PERC: + state = PositionState.MINIMUM_PROFIT + elif 0.0 <= pl_perc < self.MIN_PROFIT_PERC: + state = PositionState.BREAK_EVEN + elif self.MAX_LOSS_PERC < pl_perc < 0.0: + state = PositionState.LOSS + else: + state = PositionState.CRITICAL + + if not prev or prev.state == state: + return state, events + + if state == PositionState.PROFIT: + events.append(Event(EventKind.REACHED_GOOD_PROFIT, position.id, ss.current_tick)) + elif state == PositionState.MINIMUM_PROFIT: + events.append(Event(EventKind.REACHED_MIN_PROFIT, position.id, ss.current_tick)) + elif state == PositionState.BREAK_EVEN: + events.append(Event(EventKind.REACHED_BREAK_EVEN, position.id, ss.current_tick)) + elif state == PositionState.LOSS: + events.append(Event(EventKind.REACHED_LOSS, position.id, ss.current_tick)) + else: + events.append(Event(EventKind.REACHED_MAX_LOSS, position.id, ss.current_tick)) + events.append(Event(EventKind.CLOSE_POSITION, position.id, ss.current_tick)) + + return state, events + + From 074de41e57853bd985c06b964d7f2c999fccf0c4 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 7 Dec 2020 12:27:32 +0000 Subject: [PATCH 2/3] raising exceptions if keys are not defined --- bfxbot/bfxbot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py index 10ff142..13a32f8 100644 --- a/bfxbot/bfxbot.py +++ b/bfxbot/bfxbot.py @@ -8,6 +8,14 @@ from bfxbot.models import SymbolStatus, Ticker, EventHandler, Strategy class BfxBot: def __init__(self, api_key: str, api_secret: str, tick_duration: int = 1): + if api_key is None: + print("API_KEY is not set!") + raise ValueError + + if api_secret is None: + print("API_SECRET is not set!") + raise ValueError + self.bfx: BfxWrapper = BfxWrapper(api_key, api_secret) self.status: Dict[Symbol, SymbolStatus] = {} self.ticker: Ticker = Ticker(tick_duration) From a2b9948793d3ba5d8ef7655cbdc429b4297924f7 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 7 Dec 2020 12:27:44 +0000 Subject: [PATCH 3/3] updated requirements --- requirements.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3b19e20..46e2c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,2 @@ -asciimatics==1.12.0 -asyncio==3.4.3 -playsound==1.2.2 -termcolor==1.1.0 -termplotlib==0.3.2 +python-dotenv~=0.15.0 +sympy~=1.7 \ No newline at end of file