mtcli-renko 1.1.2__tar.gz → 1.2.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/PKG-INFO +1 -1
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/models/renko_model.py +354 -328
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/views/renko_view.py +205 -200
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/pyproject.toml +1 -1
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/LICENSE +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/README.md +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/commands/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/commands/renko.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/conf.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/controllers/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/controllers/renko_controller.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/database.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/tick_cache.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/tick_repository.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/models/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/plugin.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/utils/__init__.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/utils/renko_stats.py +0 -0
- {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/views/__init__.py +0 -0
|
@@ -1,328 +1,354 @@
|
|
|
1
|
-
"""
|
|
2
|
-
RenkoModel profissional.
|
|
3
|
-
|
|
4
|
-
Candle mode determinístico
|
|
5
|
-
Tick mode híbrido (confirmados + em formação)
|
|
6
|
-
Ancoragem correta na abertura da B3
|
|
7
|
-
Ajuste UTC da corretora
|
|
8
|
-
Margem de segurança na abertura
|
|
9
|
-
Reconstrução de caminho do candle (path reconstruction)
|
|
10
|
-
Compatível com controller atual
|
|
11
|
-
Funciona mesmo com mercado fechado
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from dataclasses import dataclass
|
|
15
|
-
from typing import List, Optional, NamedTuple
|
|
16
|
-
from datetime import datetime, time as dtime, timedelta
|
|
17
|
-
|
|
18
|
-
import MetaTrader5 as mt5
|
|
19
|
-
|
|
20
|
-
from mtcli.mt5_context import mt5_conexao
|
|
21
|
-
from mtcli.logger import setup_logger
|
|
22
|
-
from ..marketdata.tick_repository import TickRepository
|
|
23
|
-
from ..conf import SESSION_OPEN, SESSION_OPEN_OFFSET_SECONDS, BROKER_UTC_OFFSET
|
|
24
|
-
|
|
25
|
-
log = setup_logger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# ==========================================================
|
|
29
|
-
# DATA STRUCTURES
|
|
30
|
-
# ==========================================================
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class Brick:
|
|
34
|
-
direction: str
|
|
35
|
-
open: float
|
|
36
|
-
close: float
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# ==========================================================
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
self.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# ======================================================
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
abertura_utc
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# ======================================================
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# ======================================================
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# ======================================================
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
# ======================================================
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# =============================
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# =============================
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# ======================================================
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
1
|
+
"""
|
|
2
|
+
RenkoModel profissional.
|
|
3
|
+
|
|
4
|
+
Candle mode determinístico
|
|
5
|
+
Tick mode híbrido (confirmados + em formação)
|
|
6
|
+
Ancoragem correta na abertura da B3
|
|
7
|
+
Ajuste UTC da corretora
|
|
8
|
+
Margem de segurança na abertura
|
|
9
|
+
Reconstrução de caminho do candle (path reconstruction)
|
|
10
|
+
Compatível com controller atual
|
|
11
|
+
Funciona mesmo com mercado fechado
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import List, Optional, NamedTuple
|
|
16
|
+
from datetime import datetime, time as dtime, timedelta
|
|
17
|
+
|
|
18
|
+
import MetaTrader5 as mt5
|
|
19
|
+
|
|
20
|
+
from mtcli.mt5_context import mt5_conexao
|
|
21
|
+
from mtcli.logger import setup_logger
|
|
22
|
+
from ..marketdata.tick_repository import TickRepository
|
|
23
|
+
from ..conf import SESSION_OPEN, SESSION_OPEN_OFFSET_SECONDS, BROKER_UTC_OFFSET
|
|
24
|
+
|
|
25
|
+
log = setup_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ==========================================================
|
|
29
|
+
# DATA STRUCTURES
|
|
30
|
+
# ==========================================================
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Brick:
|
|
34
|
+
direction: str
|
|
35
|
+
open: float
|
|
36
|
+
close: float
|
|
37
|
+
volume: float = 0
|
|
38
|
+
ticks: int = 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RenkoTickResult(NamedTuple):
|
|
42
|
+
confirmados: List[Brick]
|
|
43
|
+
em_formacao: Optional[Brick]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ==========================================================
|
|
47
|
+
# MODEL
|
|
48
|
+
# ==========================================================
|
|
49
|
+
|
|
50
|
+
class RenkoModel:
|
|
51
|
+
|
|
52
|
+
def __init__(self, symbol: str, brick_size: float):
|
|
53
|
+
|
|
54
|
+
self.symbol = symbol
|
|
55
|
+
self.brick_size = brick_size
|
|
56
|
+
self.repo = TickRepository()
|
|
57
|
+
|
|
58
|
+
# ======================================================
|
|
59
|
+
# UTIL
|
|
60
|
+
# ======================================================
|
|
61
|
+
|
|
62
|
+
def _session_start_timestamp(self, data):
|
|
63
|
+
|
|
64
|
+
abertura_b3 = datetime.combine(
|
|
65
|
+
data,
|
|
66
|
+
dtime.fromisoformat(SESSION_OPEN),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
abertura_utc = abertura_b3 + timedelta(hours=BROKER_UTC_OFFSET)
|
|
70
|
+
|
|
71
|
+
abertura_utc += timedelta(seconds=SESSION_OPEN_OFFSET_SECONDS)
|
|
72
|
+
|
|
73
|
+
return int(abertura_utc.timestamp())
|
|
74
|
+
|
|
75
|
+
# ======================================================
|
|
76
|
+
# RATES (CANDLE MODE)
|
|
77
|
+
# ======================================================
|
|
78
|
+
|
|
79
|
+
def obter_rates(self, timeframe, quantidade, ancorar_abertura=False):
|
|
80
|
+
|
|
81
|
+
with mt5_conexao():
|
|
82
|
+
|
|
83
|
+
if not mt5.symbol_select(self.symbol, True):
|
|
84
|
+
raise RuntimeError(f"Erro ao selecionar símbolo {self.symbol}")
|
|
85
|
+
|
|
86
|
+
if quantidade == 0:
|
|
87
|
+
quantidade = 1000
|
|
88
|
+
|
|
89
|
+
rates = mt5.copy_rates_from_pos(
|
|
90
|
+
self.symbol,
|
|
91
|
+
timeframe,
|
|
92
|
+
0,
|
|
93
|
+
quantidade,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if rates is None or len(rates) == 0:
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
if not ancorar_abertura:
|
|
100
|
+
return rates
|
|
101
|
+
|
|
102
|
+
ultimo_ts = int(rates[-1]["time"])
|
|
103
|
+
|
|
104
|
+
ultimo_dia = datetime.utcfromtimestamp(ultimo_ts).date()
|
|
105
|
+
|
|
106
|
+
abertura_ts = self._session_start_timestamp(ultimo_dia)
|
|
107
|
+
|
|
108
|
+
filtrados = []
|
|
109
|
+
|
|
110
|
+
for r in rates:
|
|
111
|
+
|
|
112
|
+
ts = int(r["time"])
|
|
113
|
+
|
|
114
|
+
if ts >= abertura_ts:
|
|
115
|
+
filtrados.append(r)
|
|
116
|
+
|
|
117
|
+
return filtrados
|
|
118
|
+
|
|
119
|
+
# ======================================================
|
|
120
|
+
# TICKS (BANCO + MT5)
|
|
121
|
+
# ======================================================
|
|
122
|
+
|
|
123
|
+
def obter_ticks(self, max_ticks=5000, ancorar_abertura=False):
|
|
124
|
+
|
|
125
|
+
last_time = self.repo._get_last_tick_time(self.symbol)
|
|
126
|
+
|
|
127
|
+
if last_time is None:
|
|
128
|
+
|
|
129
|
+
self.repo.sync(self.symbol, days_back=3)
|
|
130
|
+
last_time = self.repo._get_last_tick_time(self.symbol)
|
|
131
|
+
|
|
132
|
+
else:
|
|
133
|
+
|
|
134
|
+
self.repo.sync(self.symbol)
|
|
135
|
+
|
|
136
|
+
if last_time is None:
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
end_ts = int(datetime.utcnow().timestamp())
|
|
140
|
+
|
|
141
|
+
if ancorar_abertura:
|
|
142
|
+
|
|
143
|
+
data = datetime.utcfromtimestamp(last_time).date()
|
|
144
|
+
|
|
145
|
+
start_ts = self._session_start_timestamp(data)
|
|
146
|
+
|
|
147
|
+
else:
|
|
148
|
+
|
|
149
|
+
start_ts = 0
|
|
150
|
+
|
|
151
|
+
rows = self.repo.get_ticks_between(
|
|
152
|
+
self.symbol,
|
|
153
|
+
start_ts,
|
|
154
|
+
end_ts,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if rows is None or len(rows) == 0:
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
return rows[-max_ticks:]
|
|
161
|
+
|
|
162
|
+
# ======================================================
|
|
163
|
+
# PATH RECONSTRUCTION
|
|
164
|
+
# ======================================================
|
|
165
|
+
|
|
166
|
+
def _reconstruir_path(self, rate):
|
|
167
|
+
|
|
168
|
+
o = float(rate["open"])
|
|
169
|
+
h = float(rate["high"])
|
|
170
|
+
l = float(rate["low"])
|
|
171
|
+
c = float(rate["close"])
|
|
172
|
+
|
|
173
|
+
if c >= o:
|
|
174
|
+
return [o, l, h, c]
|
|
175
|
+
|
|
176
|
+
return [o, h, l, c]
|
|
177
|
+
|
|
178
|
+
# ======================================================
|
|
179
|
+
# RENKO CANDLE
|
|
180
|
+
# ======================================================
|
|
181
|
+
|
|
182
|
+
def construir_renko(self, rates, modo="simples") -> List[Brick]:
|
|
183
|
+
|
|
184
|
+
if rates is None or len(rates) < 2:
|
|
185
|
+
return []
|
|
186
|
+
|
|
187
|
+
bricks: List[Brick] = []
|
|
188
|
+
|
|
189
|
+
last_price = float(rates[0]["open"])
|
|
190
|
+
last_direction: Optional[str] = None
|
|
191
|
+
|
|
192
|
+
for rate in rates[1:]:
|
|
193
|
+
|
|
194
|
+
path = self._reconstruir_path(rate)
|
|
195
|
+
|
|
196
|
+
for price in path:
|
|
197
|
+
|
|
198
|
+
# =============================
|
|
199
|
+
# movimento de alta
|
|
200
|
+
# =============================
|
|
201
|
+
|
|
202
|
+
while price - last_price >= self.brick_size:
|
|
203
|
+
|
|
204
|
+
# regra reversão clássica
|
|
205
|
+
if modo == "classico" and last_direction == "down":
|
|
206
|
+
|
|
207
|
+
if price - last_price < self.brick_size * 2:
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
last_price += self.brick_size
|
|
211
|
+
|
|
212
|
+
novo = last_price + self.brick_size
|
|
213
|
+
|
|
214
|
+
bricks.append(
|
|
215
|
+
Brick("up", last_price, novo)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
last_price = novo
|
|
219
|
+
last_direction = "up"
|
|
220
|
+
|
|
221
|
+
# =============================
|
|
222
|
+
# movimento de baixa
|
|
223
|
+
# =============================
|
|
224
|
+
|
|
225
|
+
while last_price - price >= self.brick_size:
|
|
226
|
+
|
|
227
|
+
if modo == "classico" and last_direction == "up":
|
|
228
|
+
|
|
229
|
+
if last_price - price < self.brick_size * 2:
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
last_price -= self.brick_size
|
|
233
|
+
|
|
234
|
+
novo = last_price - self.brick_size
|
|
235
|
+
|
|
236
|
+
bricks.append(
|
|
237
|
+
Brick("down", last_price, novo)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
last_price = novo
|
|
241
|
+
last_direction = "down"
|
|
242
|
+
|
|
243
|
+
return bricks
|
|
244
|
+
|
|
245
|
+
# ======================================================
|
|
246
|
+
# RENKO TICK MODE
|
|
247
|
+
# ======================================================
|
|
248
|
+
|
|
249
|
+
def construir_renko_ticks(self, ticks, modo="simples") -> RenkoTickResult:
|
|
250
|
+
|
|
251
|
+
if ticks is None or len(ticks) < 2:
|
|
252
|
+
return RenkoTickResult([], None)
|
|
253
|
+
|
|
254
|
+
bricks: List[Brick] = []
|
|
255
|
+
|
|
256
|
+
last_price = float(ticks[0][3])
|
|
257
|
+
last_direction: Optional[str] = None
|
|
258
|
+
volume_acumulado = 0
|
|
259
|
+
ticks_acumulados = 0
|
|
260
|
+
|
|
261
|
+
for tick in ticks[1:]:
|
|
262
|
+
|
|
263
|
+
price = float(tick[3])
|
|
264
|
+
volume = float(tick[4])
|
|
265
|
+
|
|
266
|
+
volume_acumulado += volume
|
|
267
|
+
ticks_acumulados += 1
|
|
268
|
+
|
|
269
|
+
# =============================
|
|
270
|
+
# movimento de alta
|
|
271
|
+
# =============================
|
|
272
|
+
|
|
273
|
+
while price - last_price >= self.brick_size:
|
|
274
|
+
|
|
275
|
+
if modo == "classico" and last_direction == "down":
|
|
276
|
+
|
|
277
|
+
if price - last_price < self.brick_size * 2:
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
last_price += self.brick_size
|
|
281
|
+
|
|
282
|
+
novo = last_price + self.brick_size
|
|
283
|
+
|
|
284
|
+
bricks.append(
|
|
285
|
+
Brick(
|
|
286
|
+
direction="up",
|
|
287
|
+
open=last_price,
|
|
288
|
+
close=novo,
|
|
289
|
+
volume=volume_acumulado,
|
|
290
|
+
ticks=ticks_acumulados,
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
volume_acumulado = 0
|
|
295
|
+
ticks_acumulados = 0
|
|
296
|
+
last_price = novo
|
|
297
|
+
last_direction = "up"
|
|
298
|
+
|
|
299
|
+
# =============================
|
|
300
|
+
# movimento de baixa
|
|
301
|
+
# =============================
|
|
302
|
+
|
|
303
|
+
while last_price - price >= self.brick_size:
|
|
304
|
+
|
|
305
|
+
if modo == "classico" and last_direction == "up":
|
|
306
|
+
|
|
307
|
+
if last_price - price < self.brick_size * 2:
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
last_price -= self.brick_size
|
|
311
|
+
|
|
312
|
+
novo = last_price - self.brick_size
|
|
313
|
+
|
|
314
|
+
bricks.append(
|
|
315
|
+
Brick(
|
|
316
|
+
direction="down",
|
|
317
|
+
open=last_price,
|
|
318
|
+
close=novo,
|
|
319
|
+
volume=volume_acumulado,
|
|
320
|
+
ticks=ticks_acumulados,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
volume_acumulado = 0
|
|
325
|
+
ticks_acumulados = 0
|
|
326
|
+
last_price = novo
|
|
327
|
+
last_direction = "down"
|
|
328
|
+
|
|
329
|
+
# =================================
|
|
330
|
+
# brick em formação
|
|
331
|
+
# =================================
|
|
332
|
+
|
|
333
|
+
ultimo_preco = float(ticks[-1][3])
|
|
334
|
+
|
|
335
|
+
diferenca = ultimo_preco - last_price
|
|
336
|
+
|
|
337
|
+
em_formacao = None
|
|
338
|
+
|
|
339
|
+
if abs(diferenca) > 0:
|
|
340
|
+
|
|
341
|
+
direcao = "up" if diferenca > 0 else "down"
|
|
342
|
+
|
|
343
|
+
em_formacao = Brick(
|
|
344
|
+
direction=direcao,
|
|
345
|
+
open=last_price,
|
|
346
|
+
close=ultimo_preco,
|
|
347
|
+
volume=volume_acumulado,
|
|
348
|
+
ticks=ticks_acumulados,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return RenkoTickResult(
|
|
352
|
+
confirmados=bricks,
|
|
353
|
+
em_formacao=em_formacao,
|
|
354
|
+
)
|
|
@@ -1,200 +1,205 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Renko view acessível.
|
|
3
|
-
|
|
4
|
-
Responsável por exibir os blocos Renko no terminal de forma simples,
|
|
5
|
-
compatível com leitores de tela e ambientes CLI.
|
|
6
|
-
|
|
7
|
-
Características
|
|
8
|
-
---------------
|
|
9
|
-
|
|
10
|
-
- Saída textual simples (sem gráficos ASCII complexos)
|
|
11
|
-
- Compatível com NVDA / JAWS
|
|
12
|
-
- Suporte a numeração opcional dos blocos
|
|
13
|
-
- Suporte a símbolos configuráveis para direção dos blocos
|
|
14
|
-
|
|
15
|
-
Os símbolos utilizados são definidos em `conf.py`:
|
|
16
|
-
|
|
17
|
-
BRICK_UP
|
|
18
|
-
BRICK_DOWN
|
|
19
|
-
|
|
20
|
-
Exemplo de saída:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
import click
|
|
28
|
-
|
|
29
|
-
from ..conf import DIGITS, BRICK_UP, BRICK_DOWN
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# ==========================================================
|
|
33
|
-
# DETECÇÃO DE PADRÕES
|
|
34
|
-
# ==========================================================
|
|
35
|
-
|
|
36
|
-
def _detectar_padroes(bricks):
|
|
37
|
-
"""
|
|
38
|
-
Detecta padrões simples de price action no Renko.
|
|
39
|
-
|
|
40
|
-
Atualmente detecta:
|
|
41
|
-
|
|
42
|
-
H1
|
|
43
|
-
Primeira retomada de alta após correção.
|
|
44
|
-
|
|
45
|
-
H2
|
|
46
|
-
Segunda tentativa de continuação de alta.
|
|
47
|
-
|
|
48
|
-
L2
|
|
49
|
-
Segunda tentativa de continuação de baixa.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
if len(bricks) < 3:
|
|
53
|
-
return []
|
|
54
|
-
|
|
55
|
-
patterns = []
|
|
56
|
-
|
|
57
|
-
last = bricks[-1].direction
|
|
58
|
-
prev = bricks[-2].direction
|
|
59
|
-
prev2 = bricks[-3].direction
|
|
60
|
-
|
|
61
|
-
if last == "up" and prev == "down":
|
|
62
|
-
patterns.append("H1")
|
|
63
|
-
|
|
64
|
-
if last == "up" and prev == "down" and prev2 == "up":
|
|
65
|
-
patterns.append("H2")
|
|
66
|
-
|
|
67
|
-
if last == "down" and prev == "up" and prev2 == "down":
|
|
68
|
-
patterns.append("L2")
|
|
69
|
-
|
|
70
|
-
return patterns
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# ==========================================================
|
|
74
|
-
# MÉTRICAS
|
|
75
|
-
# ==========================================================
|
|
76
|
-
|
|
77
|
-
def _metricas(bricks):
|
|
78
|
-
"""
|
|
79
|
-
Calcula métricas básicas do gráfico Renko.
|
|
80
|
-
|
|
81
|
-
Retorna
|
|
82
|
-
-------
|
|
83
|
-
tuple
|
|
84
|
-
(up, down)
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
up = sum(1 for b in bricks if b.direction == "up")
|
|
88
|
-
down = sum(1 for b in bricks if b.direction == "down")
|
|
89
|
-
|
|
90
|
-
return up, down
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# ==========================================================
|
|
94
|
-
# RENDERIZAÇÃO
|
|
95
|
-
# ==========================================================
|
|
96
|
-
|
|
97
|
-
def exibir_renko(resultado, numerar=False):
|
|
98
|
-
"""
|
|
99
|
-
Exibe os blocos Renko no terminal.
|
|
100
|
-
|
|
101
|
-
Parameters
|
|
102
|
-
----------
|
|
103
|
-
resultado : list | RenkoTickResult
|
|
104
|
-
Resultado retornado pelo RenkoModel.
|
|
105
|
-
|
|
106
|
-
Pode ser:
|
|
107
|
-
- lista simples de bricks
|
|
108
|
-
- estrutura contendo `confirmados` e `em_formacao` (modo tick)
|
|
109
|
-
|
|
110
|
-
numerar : bool
|
|
111
|
-
Se True, adiciona numeração sequencial aos blocos.
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
if not resultado:
|
|
115
|
-
click.echo("Nenhum bloco Renko gerado.")
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
em_formacao = None
|
|
119
|
-
|
|
120
|
-
# compatível com retorno candle e tick
|
|
121
|
-
if isinstance(resultado, list):
|
|
122
|
-
bricks = resultado
|
|
123
|
-
else:
|
|
124
|
-
bricks = resultado.confirmados
|
|
125
|
-
em_formacao = resultado.em_formacao
|
|
126
|
-
|
|
127
|
-
click.echo("=== GRAFICO RENKO ===")
|
|
128
|
-
click.echo(f"Total de blocos: {len(bricks)}")
|
|
129
|
-
click.echo()
|
|
130
|
-
|
|
131
|
-
# ------------------------------------------------------
|
|
132
|
-
# MÉTRICAS
|
|
133
|
-
# ------------------------------------------------------
|
|
134
|
-
|
|
135
|
-
up, down = _metricas(bricks)
|
|
136
|
-
|
|
137
|
-
click.echo("METRICAS:")
|
|
138
|
-
click.echo(f"Up: {up}")
|
|
139
|
-
click.echo(f"Down: {down}")
|
|
140
|
-
click.echo(f"Delta: {up - down}")
|
|
141
|
-
click.echo()
|
|
142
|
-
|
|
143
|
-
# ------------------------------------------------------
|
|
144
|
-
# PADRÕES
|
|
145
|
-
# ------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
patterns = _detectar_padroes(bricks)
|
|
148
|
-
|
|
149
|
-
if patterns:
|
|
150
|
-
click.echo("PADROES:")
|
|
151
|
-
for p in patterns:
|
|
152
|
-
click.echo(p)
|
|
153
|
-
click.echo()
|
|
154
|
-
|
|
155
|
-
# ------------------------------------------------------
|
|
156
|
-
# BLOCOS CONFIRMADOS
|
|
157
|
-
# ------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
simbolo =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
f"{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1
|
+
"""
|
|
2
|
+
Renko view acessível.
|
|
3
|
+
|
|
4
|
+
Responsável por exibir os blocos Renko no terminal de forma simples,
|
|
5
|
+
compatível com leitores de tela e ambientes CLI.
|
|
6
|
+
|
|
7
|
+
Características
|
|
8
|
+
---------------
|
|
9
|
+
|
|
10
|
+
- Saída textual simples (sem gráficos ASCII complexos)
|
|
11
|
+
- Compatível com NVDA / JAWS
|
|
12
|
+
- Suporte a numeração opcional dos blocos
|
|
13
|
+
- Suporte a símbolos configuráveis para direção dos blocos
|
|
14
|
+
|
|
15
|
+
Os símbolos utilizados são definidos em `conf.py`:
|
|
16
|
+
|
|
17
|
+
BRICK_UP
|
|
18
|
+
BRICK_DOWN
|
|
19
|
+
|
|
20
|
+
Exemplo de saída:
|
|
21
|
+
|
|
22
|
+
up 128400 128460
|
|
23
|
+
up 128460 128520
|
|
24
|
+
down 128520 128460
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import click
|
|
28
|
+
|
|
29
|
+
from ..conf import DIGITS, BRICK_UP, BRICK_DOWN
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ==========================================================
|
|
33
|
+
# DETECÇÃO DE PADRÕES
|
|
34
|
+
# ==========================================================
|
|
35
|
+
|
|
36
|
+
def _detectar_padroes(bricks):
|
|
37
|
+
"""
|
|
38
|
+
Detecta padrões simples de price action no Renko.
|
|
39
|
+
|
|
40
|
+
Atualmente detecta:
|
|
41
|
+
|
|
42
|
+
H1
|
|
43
|
+
Primeira retomada de alta após correção.
|
|
44
|
+
|
|
45
|
+
H2
|
|
46
|
+
Segunda tentativa de continuação de alta.
|
|
47
|
+
|
|
48
|
+
L2
|
|
49
|
+
Segunda tentativa de continuação de baixa.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
if len(bricks) < 3:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
patterns = []
|
|
56
|
+
|
|
57
|
+
last = bricks[-1].direction
|
|
58
|
+
prev = bricks[-2].direction
|
|
59
|
+
prev2 = bricks[-3].direction
|
|
60
|
+
|
|
61
|
+
if last == "up" and prev == "down":
|
|
62
|
+
patterns.append("H1")
|
|
63
|
+
|
|
64
|
+
if last == "up" and prev == "down" and prev2 == "up":
|
|
65
|
+
patterns.append("H2")
|
|
66
|
+
|
|
67
|
+
if last == "down" and prev == "up" and prev2 == "down":
|
|
68
|
+
patterns.append("L2")
|
|
69
|
+
|
|
70
|
+
return patterns
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ==========================================================
|
|
74
|
+
# MÉTRICAS
|
|
75
|
+
# ==========================================================
|
|
76
|
+
|
|
77
|
+
def _metricas(bricks):
|
|
78
|
+
"""
|
|
79
|
+
Calcula métricas básicas do gráfico Renko.
|
|
80
|
+
|
|
81
|
+
Retorna
|
|
82
|
+
-------
|
|
83
|
+
tuple
|
|
84
|
+
(up, down)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
up = sum(1 for b in bricks if b.direction == "up")
|
|
88
|
+
down = sum(1 for b in bricks if b.direction == "down")
|
|
89
|
+
|
|
90
|
+
return up, down
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ==========================================================
|
|
94
|
+
# RENDERIZAÇÃO
|
|
95
|
+
# ==========================================================
|
|
96
|
+
|
|
97
|
+
def exibir_renko(resultado, numerar=False):
|
|
98
|
+
"""
|
|
99
|
+
Exibe os blocos Renko no terminal.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
resultado : list | RenkoTickResult
|
|
104
|
+
Resultado retornado pelo RenkoModel.
|
|
105
|
+
|
|
106
|
+
Pode ser:
|
|
107
|
+
- lista simples de bricks
|
|
108
|
+
- estrutura contendo `confirmados` e `em_formacao` (modo tick)
|
|
109
|
+
|
|
110
|
+
numerar : bool
|
|
111
|
+
Se True, adiciona numeração sequencial aos blocos.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
if not resultado:
|
|
115
|
+
click.echo("Nenhum bloco Renko gerado.")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
em_formacao = None
|
|
119
|
+
|
|
120
|
+
# compatível com retorno candle e tick
|
|
121
|
+
if isinstance(resultado, list):
|
|
122
|
+
bricks = resultado
|
|
123
|
+
else:
|
|
124
|
+
bricks = resultado.confirmados
|
|
125
|
+
em_formacao = resultado.em_formacao
|
|
126
|
+
|
|
127
|
+
click.echo("=== GRAFICO RENKO ===")
|
|
128
|
+
click.echo(f"Total de blocos: {len(bricks)}")
|
|
129
|
+
click.echo()
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------
|
|
132
|
+
# MÉTRICAS
|
|
133
|
+
# ------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
up, down = _metricas(bricks)
|
|
136
|
+
|
|
137
|
+
click.echo("METRICAS:")
|
|
138
|
+
click.echo(f"Up: {up}")
|
|
139
|
+
click.echo(f"Down: {down}")
|
|
140
|
+
click.echo(f"Delta: {up - down}")
|
|
141
|
+
click.echo()
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------
|
|
144
|
+
# PADRÕES
|
|
145
|
+
# ------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
patterns = _detectar_padroes(bricks)
|
|
148
|
+
|
|
149
|
+
if patterns:
|
|
150
|
+
click.echo("PADROES:")
|
|
151
|
+
for p in patterns:
|
|
152
|
+
click.echo(p)
|
|
153
|
+
click.echo()
|
|
154
|
+
|
|
155
|
+
# ------------------------------------------------------
|
|
156
|
+
# BLOCOS CONFIRMADOS
|
|
157
|
+
# ------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
click.echo("Direcao Abertura Fechamento Volume")
|
|
160
|
+
|
|
161
|
+
for i, brick in enumerate(bricks, start=1):
|
|
162
|
+
|
|
163
|
+
if brick.direction == "up":
|
|
164
|
+
simbolo = BRICK_UP
|
|
165
|
+
else:
|
|
166
|
+
simbolo = BRICK_DOWN
|
|
167
|
+
|
|
168
|
+
if numerar:
|
|
169
|
+
linha = (
|
|
170
|
+
f"{i} {simbolo} "
|
|
171
|
+
f"{brick.open:.{DIGITS}f} "
|
|
172
|
+
f"{brick.close:.{DIGITS}f} "
|
|
173
|
+
f"{brick.volume:.0f}"
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
linha = (
|
|
177
|
+
f"{simbolo} "
|
|
178
|
+
f"{brick.open:.{DIGITS}f} "
|
|
179
|
+
f"{brick.close:.{DIGITS}f} "
|
|
180
|
+
f"{brick.volume:.0f}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
click.echo(linha)
|
|
184
|
+
|
|
185
|
+
# ------------------------------------------------------
|
|
186
|
+
# BLOCO EM FORMAÇÃO (modo híbrido)
|
|
187
|
+
# ------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
if em_formacao:
|
|
190
|
+
|
|
191
|
+
click.echo()
|
|
192
|
+
|
|
193
|
+
if em_formacao.direction == "up":
|
|
194
|
+
simbolo = BRICK_UP
|
|
195
|
+
else:
|
|
196
|
+
simbolo = BRICK_DOWN
|
|
197
|
+
|
|
198
|
+
linha = (
|
|
199
|
+
f"FORMANDO {simbolo} "
|
|
200
|
+
f"{em_formacao.open:.{DIGITS}f} "
|
|
201
|
+
f"{em_formacao.close:.{DIGITS}f} "
|
|
202
|
+
f"{em_formacao.volume:.0f}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
click.echo(linha)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|