diff --git a/algobot/enums.py b/algobot/enums.py index 221d81f9..65d35ead 100644 --- a/algobot/enums.py +++ b/algobot/enums.py @@ -1,4 +1,5 @@ # TODO: Add unit tests. +from typing import Optional BULLISH = 1 BEARISH = -1 @@ -17,8 +18,31 @@ class GraphType: LONG = 1 SHORT = -1 -TRAILING = 2 -STOP = 1 + +class OrderType: + TRAILING = 2 + STOP = 1 + + @staticmethod + def from_str(value: str) -> int: + if value.lower() == "trailing": + return OrderType.TRAILING + elif value.lower() == "stop": + return OrderType.STOP + else: + raise ValueError(f"{value} is unsupported") + + @staticmethod + def to_str(order_type: Optional[int]) -> str: + if order_type == OrderType.STOP: + return "Stop" + elif order_type == OrderType.TRAILING: + return "Trailing" + elif order_type is None: + return "None" + else: + raise ValueError(f"Unknown OrderType with value {order_type}") + BACKTEST = 2 SIMULATION = 3 diff --git a/algobot/interface/configuration.py b/algobot/interface/configuration.py index e94c5554..73dbbc72 100644 --- a/algobot/interface/configuration.py +++ b/algobot/interface/configuration.py @@ -7,7 +7,7 @@ QLabel, QLayout, QMainWindow, QSpinBox, QTabWidget) -from algobot.enums import BACKTEST, LIVE, OPTIMIZER, SIMULATION, STOP, TRAILING +from algobot.enums import BACKTEST, LIVE, OPTIMIZER, SIMULATION, OrderType from algobot.graph_helpers import create_infinite_line from algobot.helpers import ROOT_DIR from algobot.interface.config_utils.credential_utils import load_credentials @@ -344,9 +344,9 @@ def get_take_profit_settings(self, caller) -> dict: dictionary = self.takeProfitDict if dictionary[tab, 'groupBox'].isChecked(): if dictionary[tab, 'takeProfitType'].currentText() == "Trailing": - takeProfitType = TRAILING + takeProfitType = OrderType.TRAILING else: - takeProfitType = STOP + takeProfitType = OrderType.STOP else: takeProfitType = None @@ -381,21 +381,22 @@ def get_loss_settings(self, caller: int) -> dict: tab = self.get_category_tab(caller) dictionary = self.lossDict if dictionary[tab, 'groupBox'].isChecked(): - lossType = TRAILING if dictionary[tab, "lossType"].currentText() == "Trailing" else STOP + loss_type = dictionary[tab, "lossType"].currentText() + loss_strategy = OrderType.TRAILING if loss_type == "Trailing" else OrderType.STOP else: - lossType = None + loss_strategy = None - lossSettings = { - 'lossType': lossType, + loss_settings = { + 'lossType': loss_strategy, 'lossTypeIndex': dictionary[tab, "lossType"].currentIndex(), 'lossPercentage': dictionary[tab, 'lossPercentage'].value(), 'smartStopLossCounter': dictionary[tab, 'smartStopLossCounter'].value() } if tab != self.backtestConfigurationTabWidget: - lossSettings['safetyTimer'] = dictionary[tab, 'safetyTimer'].value() + loss_settings['safetyTimer'] = dictionary[tab, 'safetyTimer'].value() - return lossSettings + return loss_settings def add_strategy_to_config(self, caller: int, strategyName: str, config: dict): """ diff --git a/algobot/traders/backtester.py b/algobot/traders/backtester.py index 85067612..0ac6425f 100644 --- a/algobot/traders/backtester.py +++ b/algobot/traders/backtester.py @@ -12,7 +12,8 @@ from dateutil import parser from algobot.enums import (BACKTEST, BEARISH, BULLISH, ENTER_LONG, ENTER_SHORT, - EXIT_LONG, EXIT_SHORT, LONG, OPTIMIZER, SHORT) + EXIT_LONG, EXIT_SHORT, LONG, OPTIMIZER, SHORT, + OrderType) from algobot.helpers import (LOG_FOLDER, ROOT_DIR, convert_all_dates_to_datetime, convert_small_interval, get_interval_minutes, @@ -442,7 +443,7 @@ def get_basic_optimize_info(self, run: int, totalRuns: int, result: str = 'PASSE round(self.get_net() / self.startingBalance * 100 - 100, 2), self.get_stop_loss_strategy_string(), self.get_safe_rounded_string(self.lossPercentageDecimal, multiplier=100, symbol='%'), - self.get_trailing_or_stop_type_string(self.takeProfitType), + OrderType.to_str(self.takeProfitType), self.get_safe_rounded_string(self.takeProfitPercentageDecimal, multiplier=100, symbol='%'), self.symbol, self.interval, @@ -479,11 +480,11 @@ def apply_general_settings(self, settings: Dict[str, Union[float, str, dict]]): :param settings: Dictionary with keys and values to set. """ if 'takeProfitType' in settings: - self.takeProfitType = self.get_enum_from_str(settings['takeProfitType']) + self.takeProfitType = OrderType.from_str(settings['takeProfitType']) self.takeProfitPercentageDecimal = settings['takeProfitPercentage'] / 100 if 'lossType' in settings: - self.lossStrategy = self.get_enum_from_str(settings['lossType']) + self.lossStrategy = OrderType.from_str(settings['lossType']) self.lossPercentageDecimal = settings['lossPercentage'] / 100 if 'stopLossCounter' in settings: diff --git a/algobot/traders/simulationtrader.py b/algobot/traders/simulationtrader.py index 27c9ff9c..c68a4d73 100644 --- a/algobot/traders/simulationtrader.py +++ b/algobot/traders/simulationtrader.py @@ -5,7 +5,7 @@ from algobot.data import Data from algobot.enums import (BEARISH, BULLISH, ENTER_LONG, ENTER_SHORT, - EXIT_LONG, EXIT_SHORT, LONG, SHORT) + EXIT_LONG, EXIT_SHORT, LONG, SHORT, OrderType) from algobot.helpers import convert_small_interval, get_logger from algobot.traders.trader import Trader @@ -107,7 +107,7 @@ def get_grouped_statistics(self) -> dict: if self.takeProfitType is not None: groupedDict['takeProfit'] = { - 'takeProfitType': self.get_trailing_or_stop_type_string(self.takeProfitType), + 'takeProfitType': OrderType.to_str(self.takeProfitType), 'takeProfitPercentage': self.get_safe_rounded_percentage(self.takeProfitPercentageDecimal), 'trailingTakeProfitActivated': str(self.trailingTakeProfitActivated), 'takeProfitPoint': self.get_safe_rounded_string(self.takeProfitPoint), diff --git a/algobot/traders/trader.py b/algobot/traders/trader.py index a7266109..9b27d61e 100644 --- a/algobot/traders/trader.py +++ b/algobot/traders/trader.py @@ -5,7 +5,7 @@ from typing import Dict, List, Union from algobot.enums import (BEARISH, BULLISH, ENTER_LONG, ENTER_SHORT, - EXIT_LONG, EXIT_SHORT, LONG, SHORT, STOP, TRAILING) + EXIT_LONG, EXIT_SHORT, LONG, SHORT, OrderType) from algobot.helpers import get_label_string, parse_strategy_name from algobot.strategies.strategy import Strategy @@ -224,16 +224,16 @@ def get_stop_loss(self): if self.currentPosition == SHORT: if self.smartStopLossEnter and self.previousStopLoss > self.currentPrice: self.stopLoss = self.previousStopLoss - elif self.lossStrategy == TRAILING: + elif self.lossStrategy == OrderType.TRAILING: self.stopLoss = self.shortTrailingPrice * (1 + self.lossPercentageDecimal) - elif self.lossStrategy == STOP: + elif self.lossStrategy == OrderType.STOP: self.stopLoss = self.sellShortPrice * (1 + self.lossPercentageDecimal) elif self.currentPosition == LONG: if self.smartStopLossEnter and self.previousStopLoss < self.currentPrice: self.stopLoss = self.previousStopLoss - elif self.lossStrategy == TRAILING: + elif self.lossStrategy == OrderType.TRAILING: self.stopLoss = self.longTrailingPrice * (1 - self.lossPercentageDecimal) - elif self.lossStrategy == STOP: + elif self.lossStrategy == OrderType.STOP: self.stopLoss = self.buyLongPrice * (1 - self.lossPercentageDecimal) if self.stopLoss is not None: # This is for the smart stop loss to reenter position. @@ -246,9 +246,9 @@ def get_stop_loss_strategy_string(self) -> str: Returns stop loss strategy in string format, instead of integer enum. :return: Stop loss strategy in string format. """ - if self.lossStrategy == STOP: + if self.lossStrategy == OrderType.STOP: return 'Stop Loss' - elif self.lossStrategy == TRAILING: + elif self.lossStrategy == OrderType.TRAILING: return 'Trailing Loss' elif self.lossStrategy is None: return 'None' @@ -320,28 +320,6 @@ def get_profit_percentage(initialNet: float, finalNet: float) -> float: else: return -1 * (100 - finalNet / initialNet * 100) - @staticmethod - def get_trailing_or_stop_type_string(stopType: Union[int, None]) -> str: - """ - Returns stop type in string format instead of integer enum. - :return: Stop type in string format. - """ - if stopType == STOP: - return 'Stop' - elif stopType == TRAILING: - return 'Trailing' - elif stopType is None: - return 'None' - else: - raise ValueError("Unknown type of exit position type.") - - @staticmethod - def get_enum_from_str(string): - if string.lower() == "trailing": - return TRAILING - elif string.lower() == 'stop': - return STOP - @staticmethod def get_trend_string(trend) -> str: """ @@ -436,12 +414,12 @@ def get_take_profit(self) -> Union[float, None]: return None if self.currentPosition == SHORT: - if self.takeProfitType == STOP: + if self.takeProfitType == OrderType.STOP: self.takeProfitPoint = self.sellShortPrice * (1 - self.takeProfitPercentageDecimal) else: raise ValueError("Invalid type of take profit type provided.") elif self.currentPosition == LONG: - if self.takeProfitType == STOP: + if self.takeProfitType == OrderType.STOP: self.takeProfitPoint = self.buyLongPrice * (1 + self.takeProfitPercentageDecimal) else: raise ValueError("Invalid type of take profit type provided.") diff --git a/tests/test_backtester.py b/tests/test_backtester.py index 83c2d3e7..f0c31929 100644 --- a/tests/test_backtester.py +++ b/tests/test_backtester.py @@ -4,7 +4,7 @@ import pytest -from algobot.enums import LONG, SHORT, STOP, TRAILING +from algobot.enums import LONG, SHORT, OrderType from algobot.helpers import convert_all_dates_to_datetime, load_from_csv from algobot.traders.backtester import Backtester @@ -25,8 +25,8 @@ def setUp(self) -> None: symbol="1INCHUSDT", marginEnabled=True, ) - self.backtester.apply_take_profit_settings({'takeProfitType': TRAILING, 'takeProfitPercentage': 5}) - self.backtester.apply_loss_settings({'lossType': TRAILING, 'lossPercentage': 5}) + self.backtester.apply_take_profit_settings({'takeProfitType': OrderType.TRAILING, 'takeProfitPercentage': 5}) + self.backtester.apply_loss_settings({'lossType': OrderType.TRAILING, 'lossPercentage': 5}) def test_initialization(self): """ @@ -281,7 +281,7 @@ def test_long_stop_loss(self): Test backtester stop loss logic in a long position. """ backtester = self.backtester - backtester.lossStrategy = STOP + backtester.lossStrategy = OrderType.STOP backtester.set_priced_current_price_and_period(5) backtester.buy_long("Test purchase.") self.assertEqual(backtester.get_stop_loss(), 5 * (1 - backtester.lossPercentageDecimal)) @@ -289,7 +289,7 @@ def test_long_stop_loss(self): backtester.set_priced_current_price_and_period(10) self.assertEqual(backtester.get_stop_loss(), 5 * (1 - backtester.lossPercentageDecimal)) - backtester.lossStrategy = TRAILING + backtester.lossStrategy = OrderType.TRAILING self.assertEqual(backtester.get_stop_loss(), 10 * (1 - backtester.lossPercentageDecimal)) def test_short_stop_loss(self): @@ -297,7 +297,7 @@ def test_short_stop_loss(self): Test backtester stop loss logic in a short position. """ backtester = self.backtester - backtester.lossStrategy = STOP + backtester.lossStrategy = OrderType.STOP backtester.set_priced_current_price_and_period(5) backtester.sell_short("Test short.") self.assertEqual(backtester.get_stop_loss(), 5 * (1 + backtester.lossPercentageDecimal)) @@ -305,7 +305,7 @@ def test_short_stop_loss(self): backtester.set_priced_current_price_and_period(3) self.assertEqual(backtester.get_stop_loss(), 5 * (1 + backtester.lossPercentageDecimal)) - backtester.lossStrategy = TRAILING + backtester.lossStrategy = OrderType.TRAILING self.assertEqual(backtester.get_stop_loss(), 3 * (1 + backtester.lossPercentageDecimal)) def test_stop_take_profit(self): @@ -313,7 +313,7 @@ def test_stop_take_profit(self): Test backtester take profit logic. """ backtester = self.backtester - backtester.takeProfitType = STOP + backtester.takeProfitType = OrderType.STOP backtester.set_priced_current_price_and_period(10) backtester.buy_long("Test purchase.") self.assertEqual(backtester.get_take_profit(), 10 * (1 + backtester.takeProfitPercentageDecimal)) diff --git a/tests/test_base_trader.py b/tests/test_base_trader.py index 7740c324..17c17bf6 100644 --- a/tests/test_base_trader.py +++ b/tests/test_base_trader.py @@ -2,7 +2,7 @@ import pytest -from algobot.enums import BEARISH, BULLISH, LONG, SHORT, STOP, TRAILING +from algobot.enums import BEARISH, BULLISH, LONG, SHORT, OrderType from algobot.strategies.strategy import Strategy from algobot.traders.trader import Trader @@ -149,30 +149,30 @@ def test_set_safety_timer(self): def test_apply_take_profit_settings(self): take_profit_settings = { 'takeProfitPercentage': 25, - 'takeProfitType': STOP + 'takeProfitType': OrderType.STOP } self.trader.apply_take_profit_settings(take_profit_settings) self.assertEqual(self.trader.takeProfitPercentageDecimal, 0.25) - self.assertEqual(self.trader.takeProfitType, STOP) + self.assertEqual(self.trader.takeProfitType, OrderType.STOP) def test_apply_loss_settings(self): loss_settings = { - 'lossType': STOP, + 'lossType': OrderType.STOP, 'lossPercentage': 5.5, 'smartStopLossCounter': 15, 'safetyTimer': 45 } self.trader.apply_loss_settings(loss_settings) - self.assertEqual(self.trader.lossStrategy, STOP) + self.assertEqual(self.trader.lossStrategy, OrderType.STOP) self.assertEqual(self.trader.lossPercentageDecimal, 0.055) self.assertEqual(self.trader.smartStopLossInitialCounter, 15) self.assertEqual(self.trader.smartStopLossCounter, 15) self.assertEqual(self.trader.safetyTimer, 45) def test_get_stop_loss(self): - self.trader.lossStrategy = STOP + self.trader.lossStrategy = OrderType.STOP self.trader.lossPercentageDecimal = 0.1 self.trader.currentPrice = 5 @@ -190,10 +190,10 @@ def test_get_stop_loss(self): # TODO implement trailing stop loss test def test_get_stop_loss_strategy_string(self): - self.trader.lossStrategy = STOP + self.trader.lossStrategy = OrderType.STOP self.assertEqual(self.trader.get_stop_loss_strategy_string(), "Stop Loss") - self.trader.lossStrategy = TRAILING + self.trader.lossStrategy = OrderType.TRAILING self.assertEqual(self.trader.get_stop_loss_strategy_string(), "Trailing Loss") self.trader.lossStrategy = None @@ -273,11 +273,6 @@ def test_get_profit_percentage(self): self.assertEqual(self.trader.get_profit_percentage(100, 50), -50) self.assertEqual(self.trader.get_profit_percentage(100, 130), 30) - def test_get_trailing_or_stop_loss_string(self): - self.assertEqual(self.trader.get_trailing_or_stop_type_string(STOP), 'Stop') - self.assertEqual(self.trader.get_trailing_or_stop_type_string(TRAILING), 'Trailing') - self.assertEqual(self.trader.get_trailing_or_stop_type_string(None), 'None') - def test_get_trend_string(self): self.assertEqual(self.trader.get_trend_string(None), str(None)) self.assertEqual(self.trader.get_trend_string(BEARISH), "Bearish") @@ -321,7 +316,7 @@ def test_get_safe_rounded_string(self): multiplier=5), '6.15*') def test_get_take_profit(self): - self.trader.takeProfitType = STOP + self.trader.takeProfitType = OrderType.STOP self.trader.takeProfitPercentageDecimal = 0.05 self.trader.currentPosition = LONG diff --git a/tests/test_enums.py b/tests/test_enums.py new file mode 100644 index 00000000..db1380ca --- /dev/null +++ b/tests/test_enums.py @@ -0,0 +1,22 @@ +import unittest + +from algobot.enums import OrderType + + +class OrderTypeTest(unittest.TestCase): + def test_from_str(self): + self.assertEqual(OrderType.from_str("Stop"), OrderType.STOP) + self.assertEqual(OrderType.from_str("Trailing"), OrderType.TRAILING) + + def test_from_str_unsupported(self): + with self.assertRaises(ValueError): + OrderType.from_str("Random") + + def test_to_str(self): + self.assertEqual(OrderType.to_str(OrderType.STOP), "Stop") + self.assertEqual(OrderType.to_str(OrderType.TRAILING), "Trailing") + self.assertEqual(OrderType.to_str(None), "None") + + def test_to_str_unsupported(self): + with self.assertRaises(ValueError): + OrderType.to_str(100)