Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_invest-bot-main/trade_system/strategies/change_and_volume_strategy.py
5933 views
1
import logging
2
from decimal import Decimal
3
from typing import Optional
4
5
from tinkoff.invest import HistoricCandle
6
from tinkoff.invest.utils import quotation_to_decimal
7
8
from configuration.settings import StrategySettings
9
from trade_system.signal import Signal, SignalType
10
from trade_system.strategies.base_strategy import IStrategy
11
12
__all__ = ("ChangeAndVolumeStrategy")
13
14
logger = logging.getLogger(__name__)
15
16
17
class ChangeAndVolumeStrategy(IStrategy):
18
"""
19
Example of trade strategy.
20
IMPORTANT: DO NOT USE IT FOR REAL TRADING!
21
"""
22
# Consts for read and parse dict with strategy configuration
23
24
__SIGNAL_VOLUME_NAME = "SIGNAL_VOLUME"
25
__SIGNAL_MIN_CANDLES_NAME = "SIGNAL_MIN_CANDLES"
26
__LONG_TAKE_NAME = "LONG_TAKE"
27
__LONG_STOP_NAME = "LONG_STOP"
28
__SHORT_TAKE_NAME = "SHORT_TAKE"
29
__SHORT_STOP_NAME = "SHORT_STOP"
30
__SIGNAL_MIN_TAIL_NAME = "SIGNAL_MIN_TAIL"
31
32
def __init__(self, settings: StrategySettings) -> None:
33
self.__settings = settings
34
35
self.__signal_volume = int(settings.settings[self.__SIGNAL_VOLUME_NAME])
36
self.__signal_min_candles = int(settings.settings[self.__SIGNAL_MIN_CANDLES_NAME])
37
self.__signal_min_tail = Decimal(settings.settings[self.__SIGNAL_MIN_TAIL_NAME])
38
39
self.__long_take = Decimal(settings.settings[self.__LONG_TAKE_NAME])
40
self.__long_stop = Decimal(settings.settings[self.__LONG_STOP_NAME])
41
42
self.__short_take = Decimal(settings.settings[self.__SHORT_TAKE_NAME])
43
self.__short_stop = Decimal(settings.settings[self.__SHORT_STOP_NAME])
44
45
self.__recent_candles = []
46
47
@property
48
def settings(self) -> StrategySettings:
49
return self.__settings
50
51
def update_lot_count(self, lot: int) -> None:
52
self.__settings.lot_size = lot
53
54
def update_short_status(self, status: bool) -> None:
55
self.__settings.short_enabled_flag = status
56
57
def analyze_candles(self, candles: list[HistoricCandle]) -> Optional[Signal]:
58
"""
59
The method analyzes candles and returns his decision.
60
"""
61
logger.debug(f"Start analyze candles for {self.settings.figi} strategy {__name__}. "
62
f"Candles count: {len(candles)}")
63
64
if not self.__update_recent_candles(candles):
65
return None
66
67
if self.__is_match_long():
68
logger.info(f"Signal (LONG) {self.settings.figi} has been found.")
69
return self.__make_signal(SignalType.LONG, self.__long_take, self.__long_stop)
70
71
if self.settings.short_enabled_flag and self.__is_match_short():
72
logger.info(f"Signal (SHORT) {self.settings.figi} has been found.")
73
return self.__make_signal(SignalType.SHORT, self.__short_take, self.__short_stop)
74
75
return None
76
77
def __update_recent_candles(self, candles: list[HistoricCandle]) -> bool:
78
self.__recent_candles.extend(candles)
79
80
if len(self.__recent_candles) < self.__signal_min_candles:
81
logger.debug(f"Candles in cache are low than required")
82
return False
83
84
sorted(self.__recent_candles, key=lambda x: x.time)
85
86
# keep only __signal_min_candles candles in cache
87
if len(self.__recent_candles) > self.__signal_min_candles:
88
self.__recent_candles = self.__recent_candles[len(self.__recent_candles) - self.__signal_min_candles:]
89
90
return True
91
92
def __is_match_long(self) -> bool:
93
"""
94
Check for LONG signal. All candles in cache:
95
Green candle, tail lower than __signal_min_tail, volume more that __signal_volume
96
"""
97
for candle in self.__recent_candles:
98
logger.debug(f"Recent Candle to analyze {self.settings.figi} LONG: {candle}")
99
open_, high, close, low = quotation_to_decimal(candle.open), quotation_to_decimal(candle.high), \
100
quotation_to_decimal(candle.close), quotation_to_decimal(candle.low)
101
102
if open_ < close \
103
and ((high - close) / (high - low)) <= self.__signal_min_tail \
104
and candle.volume >= self.__signal_volume:
105
logger.debug(f"Continue analyze {self.settings.figi}")
106
continue
107
108
logger.debug(f"Break analyze {self.settings.figi}")
109
break
110
else:
111
logger.debug(f"Signal detected {self.settings.figi}")
112
return True
113
114
return False
115
116
def __is_match_short(self) -> bool:
117
"""
118
Check for LONG signal. All candles in cache:
119
Red candle, tail lower than __signal_min_tail, volume more that __signal_volume
120
"""
121
for candle in self.__recent_candles:
122
logger.debug(f"Recent Candle to analyze {self.settings.figi} SHORT: {candle}")
123
open_, high, close, low = quotation_to_decimal(candle.open), quotation_to_decimal(candle.high), \
124
quotation_to_decimal(candle.close), quotation_to_decimal(candle.low)
125
126
if open_ > close \
127
and ((close - low) / (high - low)) <= self.__signal_min_tail \
128
and candle.volume >= self.__signal_volume:
129
logger.debug(f"Continue analyze {self.settings.figi}")
130
continue
131
132
logger.debug(f"Break analyze {self.settings.figi}")
133
break
134
else:
135
logger.debug(f"Signal detected {self.settings.figi}")
136
return True
137
138
return False
139
140
def __make_signal(
141
self,
142
signal_type: SignalType,
143
profit_multy: Decimal,
144
stop_multy: Decimal
145
) -> Signal:
146
# take and stop based on configuration by close price level (close for last price)
147
last_candle = self.__recent_candles[len(self.__recent_candles) - 1]
148
149
signal = Signal(
150
figi=self.settings.figi,
151
signal_type=signal_type,
152
take_profit_level=quotation_to_decimal(last_candle.close) * profit_multy,
153
stop_loss_level=quotation_to_decimal(last_candle.close) * stop_multy
154
)
155
156
logger.info(f"Make Signal: {signal}")
157
158
return signal
159
160