diff --git a/core/models/position.py b/core/models/position.py index 1fc3ab3b..d0e88b82 100644 --- a/core/models/position.py +++ b/core/models/position.py @@ -9,7 +9,7 @@ from .ohlcv import OHLCV from .order import Order, OrderStatus -from .position_side import PositionSide +from .side import PositionSide from .signal import Signal diff --git a/core/models/position_side.py b/core/models/side.py similarity index 54% rename from core/models/position_side.py rename to core/models/side.py index 0eda084d..5735d8a0 100644 --- a/core/models/position_side.py +++ b/core/models/side.py @@ -7,3 +7,10 @@ class PositionSide(Enum): def __str__(self): return self.value + +class SignalSide(Enum): + BUY = "buy" + SELL = "sell" + + def __str__(self): + return self.value.upper() diff --git a/core/models/signal.py b/core/models/signal.py index 0df8d4b3..73084185 100644 --- a/core/models/signal.py +++ b/core/models/signal.py @@ -1,19 +1,11 @@ from dataclasses import dataclass -from enum import Enum +from .side import SignalSide from .strategy import Strategy from .symbol import Symbol from .timeframe import Timeframe -class SignalSide(Enum): - BUY = "buy" - SELL = "sell" - - def __str__(self): - return self.value.upper() - - @dataclass(frozen=True) class Signal: symbol: Symbol diff --git a/exchange/_bybit.py b/exchange/_bybit.py index 6678a240..fe0f55ae 100644 --- a/exchange/_bybit.py +++ b/exchange/_bybit.py @@ -10,7 +10,7 @@ from core.interfaces.abstract_exchange import AbstractExchange from core.models.broker import MarginMode, PositionMode from core.models.lookback import TIMEFRAMES_TO_LOOKBACK, Lookback -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.symbol import Symbol from core.models.timeframe import Timeframe from infrastructure.retry import retry diff --git a/executor/_paper_order_actor.py b/executor/_paper_order_actor.py index 0340bc21..fc8c3ee3 100644 --- a/executor/_paper_order_actor.py +++ b/executor/_paper_order_actor.py @@ -17,7 +17,7 @@ from core.models.ohlcv import OHLCV from core.models.order import Order, OrderStatus, OrderType from core.models.position import Position -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.symbol import Symbol from core.models.timeframe import Timeframe @@ -96,7 +96,7 @@ async def _execute_order(self, event: PositionInitialized): current_position = current_position.add_order(order) - logger.info(f"Position to Open: {current_position}") + logger.debug(f"Position to Open: {current_position}") if current_position.closed: await self.tell(BrokerPositionClosed(current_position)) @@ -116,9 +116,11 @@ async def _adjust_position(self, event: RiskAdjustRequested): fill_price = total_value / size if ( - current_position.side == PositionSide.LONG and current_position.stop_loss_price > current_position.take_profit_price + current_position.side == PositionSide.LONG + and current_position.stop_loss_price > current_position.take_profit_price ) or ( - current_position.side == PositionSide.SHORT and current_position.stop_loss_price < current_position.take_profit_price + current_position.side == PositionSide.SHORT + and current_position.stop_loss_price < current_position.take_profit_price ): logger.error(f"Wrong Adjust: {current_position}") return @@ -158,7 +160,7 @@ async def _close_position(self, event: PositionCloseRequested): next_position = current_position.add_order(order) - logger.info(f"Closed Position: {next_position}") + logger.debug(f"Closed Position: {next_position}") await self.tell(BrokerPositionClosed(next_position)) diff --git a/position/_actor.py b/position/_actor.py index 6eee0d9f..1601b03f 100644 --- a/position/_actor.py +++ b/position/_actor.py @@ -21,7 +21,7 @@ ) from core.interfaces.abstract_config import AbstractConfig from core.interfaces.abstract_position_factory import AbstractPositionFactory -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.symbol import Symbol from core.models.timeframe import Timeframe diff --git a/position/_position_factory.py b/position/_position_factory.py index 8b4ae53e..a122706e 100644 --- a/position/_position_factory.py +++ b/position/_position_factory.py @@ -6,7 +6,7 @@ ) from core.models.ohlcv import OHLCV from core.models.position import Position -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.signal import Signal, SignalSide diff --git a/position/_state.py b/position/_state.py index 6fa631a7..94a02a1d 100644 --- a/position/_state.py +++ b/position/_state.py @@ -3,7 +3,7 @@ from typing import List, Optional, Tuple from core.models.position import Position -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.symbol import Symbol from core.models.timeframe import Timeframe diff --git a/position/risk/break_even.py b/position/risk/break_even.py index 62df318d..9543bb86 100644 --- a/position/risk/break_even.py +++ b/position/risk/break_even.py @@ -5,7 +5,7 @@ from core.interfaces.abstract_config import AbstractConfig from core.interfaces.abstract_position_risk_strategy import AbstractPositionRiskStrategy from core.models.ohlcv import OHLCV -from core.models.position_side import PositionSide +from core.models.side import PositionSide class PositionRiskBreakEvenStrategy(AbstractPositionRiskStrategy): diff --git a/position/risk/volatility.py b/position/risk/volatility.py deleted file mode 100644 index f3aebb25..00000000 --- a/position/risk/volatility.py +++ /dev/null @@ -1,241 +0,0 @@ -from collections import deque -from typing import List, Tuple - -import numpy as np -from sklearn.linear_model import SGDRegressor - -from core.interfaces.abstract_config import AbstractConfig -from core.interfaces.abstract_position_risk_strategy import AbstractPositionRiskStrategy -from core.models.ohlcv import OHLCV -from core.models.position_side import PositionSide - - -class PositionRiskVolatilityStrategy(AbstractPositionRiskStrategy): - def __init__(self, config_service: AbstractConfig): - super().__init__() - self.config = config_service.get("position") - self.tp_model = SGDRegressor(max_iter=50, tol=1e-3) - self.sl_model = SGDRegressor(max_iter=50, tol=1e-3) - - self.x_tp_buff = deque(maxlen=55) - self.y_tp_buff = deque(maxlen=55) - - self.x_sl_buff = deque(maxlen=55) - self.y_sl_buff = deque(maxlen=55) - self.lookback = 6 - - def next( - self, - side: PositionSide, - entry_price: float, - take_profit_price: float, - stop_loss_price: float, - ohlcvs: List[Tuple[OHLCV]], - ) -> float: - ohlcvs = ohlcvs[:] - - if len(ohlcvs) < self.lookback: - return stop_loss_price, take_profit_price - - atr = self._atr(ohlcvs, self.lookback) - price = self._hlc(ohlcvs) - mean = np.mean(price) - std = np.std(price) - - curr_price, curr_atr = price[-1], atr[-1] - - features = self._features(ohlcvs, atr) - - self.update_tp_model(features, np.array([take_profit_price])) - self.update_sl_model(features, np.array([stop_loss_price])) - - risk_value = curr_atr * self.config["risk_factor"] - sl_threshold = curr_atr * self.config["sl_factor"] - tp_threshold = curr_atr * self.config["tp_factor"] - - tsl = abs(entry_price - take_profit_price) * self.config["trl_factor"] - curr_dist = abs(entry_price - curr_price) - - high = min(ohlcvs[-self.lookback :], key=lambda x: abs(x.high - tsl)).high - low = min(ohlcvs[-self.lookback :], key=lambda x: abs(x.low - tsl)).low - - next_stop_loss, next_take_profit = stop_loss_price, take_profit_price - - upper_bound, lower_bound = mean + 2 * std, mean - 2 * std - - predict_tp = self.predict_take_profit( - upper_bound, lower_bound, take_profit_price, side, tp_threshold - ) - - predict_sl = self.predict_stop_loss( - upper_bound, lower_bound, stop_loss_price, side, sl_threshold - ) - - print( - f"PREDICT: ENTRY: {entry_price}, TP: {predict_tp}, SL: {predict_sl}, SIDE: {side}, PRICE: {curr_price}, TSL: {tsl}" - ) - - if side == PositionSide.LONG: - if predict_tp > curr_price: - next_take_profit = max(entry_price + risk_value, predict_tp) - - if predict_sl < curr_price: - next_stop_loss = max( - stop_loss_price, - low - tp_threshold, - predict_sl, - ) - - elif side == PositionSide.SHORT: - if predict_tp < curr_price: - next_take_profit = min(entry_price - risk_value, predict_tp) - - if predict_sl > curr_price: - next_stop_loss = min( - stop_loss_price, - high + sl_threshold, - predict_sl, - ) - - return next_stop_loss, next_take_profit - - def predict_take_profit( - self, - upper_bound: float, - lower_bound: float, - take_profit_price: float, - side: PositionSide, - buff: float, - ) -> float: - if len(self.x_tp_buff) < self.lookback: - return take_profit_price - - latest_X = self.x_tp_buff[-1] - prediction = self.tp_model.predict(latest_X) - - tpp = abs(prediction[0]) - - if tpp > upper_bound or tpp < lower_bound: - return take_profit_price - - if side == PositionSide.LONG: - return tpp + buff - else: - return tpp - buff - - def predict_stop_loss( - self, - upper_bound: float, - lower_bound: float, - stop_loss_price: float, - side: PositionSide, - buff: float, - ) -> float: - if len(self.x_sl_buff) < self.lookback: - return stop_loss_price - - latest_X = self.x_sl_buff[-1] - prediction = self.sl_model.predict(latest_X) - - slp = abs(prediction[0]) - - if slp > upper_bound or slp < lower_bound: - return stop_loss_price - - if side == PositionSide.LONG: - return slp - buff - else: - return slp + buff - - @staticmethod - def _atr(ohlcvs: List[OHLCV], period: int) -> List[float]: - highs, lows, closes = ( - np.array([ohlcv.high for ohlcv in ohlcvs]), - np.array([ohlcv.low for ohlcv in ohlcvs]), - np.array([ohlcv.close for ohlcv in ohlcvs]), - ) - - prev_closes = np.roll(closes, 1) - - true_ranges = np.maximum( - highs - lows, np.abs(highs - prev_closes), np.abs(lows - prev_closes) - ) - - atr = np.zeros_like(true_ranges, dtype=float) - atr[period - 1] = np.mean(true_ranges[:period]) - - for i in range(period, len(true_ranges)): - atr[i] = np.divide((atr[i - 1] * (period - 1) + true_ranges[i]), period) - - return list(atr) - - @staticmethod - def _hlc2(ohlcvs: List[OHLCV]) -> List[float]: - return [(ohlcv.high + ohlcv.low + 2 * ohlcv.close) / 4.0 for ohlcv in ohlcvs] - - @staticmethod - def _hlc(ohlcvs: List[OHLCV]) -> List[float]: - return [(ohlcv.high + ohlcv.low + ohlcv.close) / 3.0 for ohlcv in ohlcvs] - - @staticmethod - def _pp(ohlcvs: List[OHLCV]): - previous_close = ohlcvs[-2].close - previous_high = ohlcvs[-2].high - previous_low = ohlcvs[-2].low - - pivot_point = (previous_high + previous_low + previous_close) / 3 - - support1 = (2 * pivot_point) - previous_high - resistance1 = (2 * pivot_point) - previous_low - - support2 = pivot_point - (previous_high - previous_low) - resistance2 = pivot_point + (previous_high - previous_low) - - return support1, resistance1, support2, resistance2 - - def _features(self, ohlcvs: List[Tuple[OHLCV]], atr: List[float]): - support1, resistance1, support2, resistance2 = self._pp(ohlcvs) - closes = [ohlcv.close for ohlcv in ohlcvs] - mean_vol = np.mean([ohlcv.volume for ohlcv in ohlcvs]) - - mean = np.mean(closes) - std = np.std(closes) - - features = np.array( - [ - self._hlc(ohlcvs)[-1], - self._hlc2(ohlcvs)[-1], - 3 * atr[-1], - mean - 2 * std, - mean + 2 * std, - support1, - resistance1, - support2, - resistance2, - ohlcvs[-1].volume / mean_vol, - ] - ) - - features = (features - np.mean(features)) / np.std(features) - - return features.reshape(1, -1) - - def update_tp_model(self, X, y): - self.x_tp_buff.append(X) - self.y_tp_buff.append(y) - - if len(self.x_tp_buff) > self.lookback: - X_train = np.vstack(self.x_tp_buff) - y_train = np.vstack(self.y_tp_buff).ravel() - - self.tp_model.partial_fit(X_train, y_train) - - def update_sl_model(self, X, y): - self.x_sl_buff.append(X) - self.y_sl_buff.append(y) - - if len(self.x_sl_buff) > self.lookback: - X_train = np.vstack(self.x_sl_buff) - y_train = np.vstack(self.y_sl_buff).ravel() - - self.sl_model.partial_fit(X_train, y_train) diff --git a/position/take_profit/risk_reward.py b/position/take_profit/risk_reward.py index ec57321a..b92ac01b 100644 --- a/position/take_profit/risk_reward.py +++ b/position/take_profit/risk_reward.py @@ -2,7 +2,7 @@ from core.interfaces.abstract_position_take_profit_strategy import ( AbstractPositionTakeProfitStrategy, ) -from core.models.position_side import PositionSide +from core.models.side import PositionSide class PositionRiskRewardTakeProfitStrategy(AbstractPositionTakeProfitStrategy): diff --git a/risk/_actor.py b/risk/_actor.py index 3d7b1db2..df9de458 100644 --- a/risk/_actor.py +++ b/risk/_actor.py @@ -9,7 +9,7 @@ PositionClosed, PositionOpened, ) -from core.events.risk import RiskAdjustRequested, RiskThresholdBreached, RiskType +from core.events.risk import RiskThresholdBreached, RiskType from core.events.signal import ( ExitLongSignalReceived, ExitShortSignalReceived, @@ -19,7 +19,7 @@ from core.interfaces.abstract_config import AbstractConfig from core.models.ohlcv import OHLCV from core.models.position import Position -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.models.symbol import Symbol from core.models.timeframe import Timeframe @@ -142,14 +142,13 @@ async def _handle_reverse( and long_position and not short_position ): - await self.tell(RiskAdjustRequested(long_position, event.entry_price)) - + await self._process_signal_exit(long_position, event.entry_price) if ( isinstance(event, GoLongSignalReceived) and short_position and not long_position ): - await self.tell(RiskAdjustRequested(short_position, event.entry_price)) + await self._process_signal_exit(short_position, event.entry_price) async def _handle_signal_exit( self, event: Union[ExitLongSignalReceived, ExitShortSignalReceived] @@ -158,35 +157,16 @@ async def _handle_signal_exit( long_position, short_position = self._position if isinstance(event, ExitLongSignalReceived) and long_position: - exit_long = await self._process_signal_exit( + await self._process_signal_exit( long_position, event.exit_price, ) - - if ( - exit_long - and short_position - and event.exit_price > short_position.stop_loss_price - ): - await self.tell( - RiskAdjustRequested(short_position, event.exit_price) - ) - if isinstance(event, ExitShortSignalReceived) and short_position: - exit_short = await self._process_signal_exit( + await self._process_signal_exit( short_position, event.exit_price, ) - if ( - exit_short - and long_position - and event.exit_price < long_position.stop_loss_price - ): - await self.tell( - RiskAdjustRequested(long_position, event.exit_price) - ) - async def _process_position(self, position: Optional[Position]): ohlcvs = list(self._ohlcv) next_position = position diff --git a/sor/_router.py b/sor/_router.py index 0b6171fb..19411095 100644 --- a/sor/_router.py +++ b/sor/_router.py @@ -13,7 +13,7 @@ from core.interfaces.abstract_exhange_factory import AbstractExchangeFactory from core.models.exchange import ExchangeType from core.models.order import Order, OrderStatus -from core.models.position_side import PositionSide +from core.models.side import PositionSide from core.queries.account import GetBalance from core.queries.broker import GetSymbol, GetSymbols from core.queries.position import GetClosePosition, GetOpenPosition