Path: blob/master/ invest-robot-contest_invest-bot-main/trade_system/strategies/change_and_volume_strategy.py
5933 views
import logging1from decimal import Decimal2from typing import Optional34from tinkoff.invest import HistoricCandle5from tinkoff.invest.utils import quotation_to_decimal67from configuration.settings import StrategySettings8from trade_system.signal import Signal, SignalType9from trade_system.strategies.base_strategy import IStrategy1011__all__ = ("ChangeAndVolumeStrategy")1213logger = logging.getLogger(__name__)141516class ChangeAndVolumeStrategy(IStrategy):17"""18Example of trade strategy.19IMPORTANT: DO NOT USE IT FOR REAL TRADING!20"""21# Consts for read and parse dict with strategy configuration2223__SIGNAL_VOLUME_NAME = "SIGNAL_VOLUME"24__SIGNAL_MIN_CANDLES_NAME = "SIGNAL_MIN_CANDLES"25__LONG_TAKE_NAME = "LONG_TAKE"26__LONG_STOP_NAME = "LONG_STOP"27__SHORT_TAKE_NAME = "SHORT_TAKE"28__SHORT_STOP_NAME = "SHORT_STOP"29__SIGNAL_MIN_TAIL_NAME = "SIGNAL_MIN_TAIL"3031def __init__(self, settings: StrategySettings) -> None:32self.__settings = settings3334self.__signal_volume = int(settings.settings[self.__SIGNAL_VOLUME_NAME])35self.__signal_min_candles = int(settings.settings[self.__SIGNAL_MIN_CANDLES_NAME])36self.__signal_min_tail = Decimal(settings.settings[self.__SIGNAL_MIN_TAIL_NAME])3738self.__long_take = Decimal(settings.settings[self.__LONG_TAKE_NAME])39self.__long_stop = Decimal(settings.settings[self.__LONG_STOP_NAME])4041self.__short_take = Decimal(settings.settings[self.__SHORT_TAKE_NAME])42self.__short_stop = Decimal(settings.settings[self.__SHORT_STOP_NAME])4344self.__recent_candles = []4546@property47def settings(self) -> StrategySettings:48return self.__settings4950def update_lot_count(self, lot: int) -> None:51self.__settings.lot_size = lot5253def update_short_status(self, status: bool) -> None:54self.__settings.short_enabled_flag = status5556def analyze_candles(self, candles: list[HistoricCandle]) -> Optional[Signal]:57"""58The method analyzes candles and returns his decision.59"""60logger.debug(f"Start analyze candles for {self.settings.figi} strategy {__name__}. "61f"Candles count: {len(candles)}")6263if not self.__update_recent_candles(candles):64return None6566if self.__is_match_long():67logger.info(f"Signal (LONG) {self.settings.figi} has been found.")68return self.__make_signal(SignalType.LONG, self.__long_take, self.__long_stop)6970if self.settings.short_enabled_flag and self.__is_match_short():71logger.info(f"Signal (SHORT) {self.settings.figi} has been found.")72return self.__make_signal(SignalType.SHORT, self.__short_take, self.__short_stop)7374return None7576def __update_recent_candles(self, candles: list[HistoricCandle]) -> bool:77self.__recent_candles.extend(candles)7879if len(self.__recent_candles) < self.__signal_min_candles:80logger.debug(f"Candles in cache are low than required")81return False8283sorted(self.__recent_candles, key=lambda x: x.time)8485# keep only __signal_min_candles candles in cache86if len(self.__recent_candles) > self.__signal_min_candles:87self.__recent_candles = self.__recent_candles[len(self.__recent_candles) - self.__signal_min_candles:]8889return True9091def __is_match_long(self) -> bool:92"""93Check for LONG signal. All candles in cache:94Green candle, tail lower than __signal_min_tail, volume more that __signal_volume95"""96for candle in self.__recent_candles:97logger.debug(f"Recent Candle to analyze {self.settings.figi} LONG: {candle}")98open_, high, close, low = quotation_to_decimal(candle.open), quotation_to_decimal(candle.high), \99quotation_to_decimal(candle.close), quotation_to_decimal(candle.low)100101if open_ < close \102and ((high - close) / (high - low)) <= self.__signal_min_tail \103and candle.volume >= self.__signal_volume:104logger.debug(f"Continue analyze {self.settings.figi}")105continue106107logger.debug(f"Break analyze {self.settings.figi}")108break109else:110logger.debug(f"Signal detected {self.settings.figi}")111return True112113return False114115def __is_match_short(self) -> bool:116"""117Check for LONG signal. All candles in cache:118Red candle, tail lower than __signal_min_tail, volume more that __signal_volume119"""120for candle in self.__recent_candles:121logger.debug(f"Recent Candle to analyze {self.settings.figi} SHORT: {candle}")122open_, high, close, low = quotation_to_decimal(candle.open), quotation_to_decimal(candle.high), \123quotation_to_decimal(candle.close), quotation_to_decimal(candle.low)124125if open_ > close \126and ((close - low) / (high - low)) <= self.__signal_min_tail \127and candle.volume >= self.__signal_volume:128logger.debug(f"Continue analyze {self.settings.figi}")129continue130131logger.debug(f"Break analyze {self.settings.figi}")132break133else:134logger.debug(f"Signal detected {self.settings.figi}")135return True136137return False138139def __make_signal(140self,141signal_type: SignalType,142profit_multy: Decimal,143stop_multy: Decimal144) -> Signal:145# take and stop based on configuration by close price level (close for last price)146last_candle = self.__recent_candles[len(self.__recent_candles) - 1]147148signal = Signal(149figi=self.settings.figi,150signal_type=signal_type,151take_profit_level=quotation_to_decimal(last_candle.close) * profit_multy,152stop_loss_level=quotation_to_decimal(last_candle.close) * stop_multy153)154155logger.info(f"Make Signal: {signal}")156157return signal158159160