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.
Files changed (21) hide show
  1. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/PKG-INFO +1 -1
  2. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/models/renko_model.py +354 -328
  3. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/views/renko_view.py +205 -200
  4. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/pyproject.toml +1 -1
  5. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/LICENSE +0 -0
  6. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/README.md +0 -0
  7. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/__init__.py +0 -0
  8. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/commands/__init__.py +0 -0
  9. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/commands/renko.py +0 -0
  10. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/conf.py +0 -0
  11. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/controllers/__init__.py +0 -0
  12. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/controllers/renko_controller.py +0 -0
  13. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/database.py +0 -0
  14. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/__init__.py +0 -0
  15. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/tick_cache.py +0 -0
  16. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/marketdata/tick_repository.py +0 -0
  17. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/models/__init__.py +0 -0
  18. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/plugin.py +0 -0
  19. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/utils/__init__.py +0 -0
  20. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/utils/renko_stats.py +0 -0
  21. {mtcli_renko-1.1.2 → mtcli_renko-1.2.0.dev1}/mtcli_renko/views/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli-renko
3
- Version: 1.1.2
3
+ Version: 1.2.0.dev1
4
4
  Summary: Renko plugin institucional para mtcli (MetaTrader 5)
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -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
- class RenkoTickResult(NamedTuple):
40
- confirmados: List[Brick]
41
- em_formacao: Optional[Brick]
42
-
43
-
44
- # ==========================================================
45
- # MODEL
46
- # ==========================================================
47
-
48
- class RenkoModel:
49
-
50
- def __init__(self, symbol: str, brick_size: float):
51
-
52
- self.symbol = symbol
53
- self.brick_size = brick_size
54
- self.repo = TickRepository()
55
-
56
- # ======================================================
57
- # UTIL
58
- # ======================================================
59
-
60
- def _session_start_timestamp(self, data):
61
-
62
- abertura_b3 = datetime.combine(
63
- data,
64
- dtime.fromisoformat(SESSION_OPEN),
65
- )
66
-
67
- abertura_utc = abertura_b3 + timedelta(hours=BROKER_UTC_OFFSET)
68
-
69
- abertura_utc += timedelta(seconds=SESSION_OPEN_OFFSET_SECONDS)
70
-
71
- return int(abertura_utc.timestamp())
72
-
73
- # ======================================================
74
- # RATES (CANDLE MODE)
75
- # ======================================================
76
-
77
- def obter_rates(self, timeframe, quantidade, ancorar_abertura=False):
78
-
79
- with mt5_conexao():
80
-
81
- if not mt5.symbol_select(self.symbol, True):
82
- raise RuntimeError(f"Erro ao selecionar símbolo {self.symbol}")
83
-
84
- if quantidade == 0:
85
- quantidade = 1000
86
-
87
- rates = mt5.copy_rates_from_pos(
88
- self.symbol,
89
- timeframe,
90
- 0,
91
- quantidade,
92
- )
93
-
94
- if rates is None or len(rates) == 0:
95
- return []
96
-
97
- if not ancorar_abertura:
98
- return rates
99
-
100
- ultimo_ts = int(rates[-1]["time"])
101
-
102
- ultimo_dia = datetime.utcfromtimestamp(ultimo_ts).date()
103
-
104
- abertura_ts = self._session_start_timestamp(ultimo_dia)
105
-
106
- filtrados = []
107
-
108
- for r in rates:
109
-
110
- ts = int(r["time"])
111
-
112
- if ts >= abertura_ts:
113
- filtrados.append(r)
114
-
115
- return filtrados
116
-
117
- # ======================================================
118
- # TICKS (BANCO + MT5)
119
- # ======================================================
120
-
121
- def obter_ticks(self, max_ticks=5000, ancorar_abertura=False):
122
-
123
- last_time = self.repo._get_last_tick_time(self.symbol)
124
-
125
- if last_time is None:
126
-
127
- self.repo.sync(self.symbol, days_back=3)
128
- last_time = self.repo._get_last_tick_time(self.symbol)
129
-
130
- else:
131
-
132
- self.repo.sync(self.symbol)
133
-
134
- if last_time is None:
135
- return []
136
-
137
- end_ts = int(datetime.utcnow().timestamp())
138
-
139
- if ancorar_abertura:
140
-
141
- data = datetime.utcfromtimestamp(last_time).date()
142
-
143
- start_ts = self._session_start_timestamp(data)
144
-
145
- else:
146
-
147
- start_ts = 0
148
-
149
- rows = self.repo.get_ticks_between(
150
- self.symbol,
151
- start_ts,
152
- end_ts,
153
- )
154
-
155
- if rows is None or len(rows) == 0:
156
- return []
157
-
158
- return rows[-max_ticks:]
159
-
160
- # ======================================================
161
- # PATH RECONSTRUCTION
162
- # ======================================================
163
-
164
- def _reconstruir_path(self, rate):
165
-
166
- o = float(rate["open"])
167
- h = float(rate["high"])
168
- l = float(rate["low"])
169
- c = float(rate["close"])
170
-
171
- if c >= o:
172
- return [o, l, h, c]
173
-
174
- return [o, h, l, c]
175
-
176
- # ======================================================
177
- # RENKO CANDLE
178
- # ======================================================
179
-
180
- def construir_renko(self, rates, modo="simples") -> List[Brick]:
181
-
182
- if rates is None or len(rates) < 2:
183
- return []
184
-
185
- bricks: List[Brick] = []
186
-
187
- last_price = float(rates[0]["open"])
188
- last_direction: Optional[str] = None
189
-
190
- for rate in rates[1:]:
191
-
192
- path = self._reconstruir_path(rate)
193
-
194
- for price in path:
195
-
196
- # =============================
197
- # movimento de alta
198
- # =============================
199
-
200
- while price - last_price >= self.brick_size:
201
-
202
- # regra reversão clássica
203
- if modo == "classico" and last_direction == "down":
204
-
205
- if price - last_price < self.brick_size * 2:
206
- break
207
-
208
- last_price += self.brick_size
209
-
210
- novo = last_price + self.brick_size
211
-
212
- bricks.append(
213
- Brick("up", last_price, novo)
214
- )
215
-
216
- last_price = novo
217
- last_direction = "up"
218
-
219
- # =============================
220
- # movimento de baixa
221
- # =============================
222
-
223
- while last_price - price >= self.brick_size:
224
-
225
- if modo == "classico" and last_direction == "up":
226
-
227
- if last_price - price < self.brick_size * 2:
228
- break
229
-
230
- last_price -= self.brick_size
231
-
232
- novo = last_price - self.brick_size
233
-
234
- bricks.append(
235
- Brick("down", last_price, novo)
236
- )
237
-
238
- last_price = novo
239
- last_direction = "down"
240
-
241
- return bricks
242
-
243
- # ======================================================
244
- # RENKO TICK MODE
245
- # ======================================================
246
-
247
- def construir_renko_ticks(self, ticks, modo="simples") -> RenkoTickResult:
248
-
249
- if ticks is None or len(ticks) < 2:
250
- return RenkoTickResult([], None)
251
-
252
- bricks: List[Brick] = []
253
-
254
- last_price = float(ticks[0][3])
255
- last_direction: Optional[str] = None
256
-
257
- for tick in ticks[1:]:
258
-
259
- price = float(tick[3])
260
-
261
- # =============================
262
- # movimento de alta
263
- # =============================
264
-
265
- while price - last_price >= self.brick_size:
266
-
267
- if modo == "classico" and last_direction == "down":
268
-
269
- if price - last_price < self.brick_size * 2:
270
- break
271
-
272
- last_price += self.brick_size
273
-
274
- novo = last_price + self.brick_size
275
-
276
- bricks.append(
277
- Brick("up", last_price, novo)
278
- )
279
-
280
- last_price = novo
281
- last_direction = "up"
282
-
283
- # =============================
284
- # movimento de baixa
285
- # =============================
286
-
287
- while last_price - price >= self.brick_size:
288
-
289
- if modo == "classico" and last_direction == "up":
290
-
291
- if last_price - price < self.brick_size * 2:
292
- break
293
-
294
- last_price -= self.brick_size
295
-
296
- novo = last_price - self.brick_size
297
-
298
- bricks.append(
299
- Brick("down", last_price, novo)
300
- )
301
-
302
- last_price = novo
303
- last_direction = "down"
304
-
305
- # =================================
306
- # brick em formação
307
- # =================================
308
-
309
- ultimo_preco = float(ticks[-1][3])
310
-
311
- diferenca = ultimo_preco - last_price
312
-
313
- em_formacao = None
314
-
315
- if abs(diferenca) > 0:
316
-
317
- direcao = "up" if diferenca > 0 else "down"
318
-
319
- em_formacao = Brick(
320
- direction=direcao,
321
- open=last_price,
322
- close=ultimo_preco,
323
- )
324
-
325
- return RenkoTickResult(
326
- confirmados=bricks,
327
- em_formacao=em_formacao,
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
- 128400 128460
23
- 128460 128520
24
- 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
- for i, brick in enumerate(bricks, start=1):
160
-
161
- if brick.direction == "up":
162
- simbolo = BRICK_UP
163
- else:
164
- simbolo = BRICK_DOWN
165
-
166
- if numerar:
167
- linha = (
168
- f"{i} {simbolo} "
169
- f"{brick.open:.{DIGITS}f} "
170
- f"{brick.close:.{DIGITS}f}"
171
- )
172
- else:
173
- linha = (
174
- f"{simbolo} "
175
- f"{brick.open:.{DIGITS}f} "
176
- f"{brick.close:.{DIGITS}f}"
177
- )
178
-
179
- click.echo(linha)
180
-
181
- # ------------------------------------------------------
182
- # BLOCO EM FORMAÇÃO (modo híbrido)
183
- # ------------------------------------------------------
184
-
185
- if em_formacao:
186
-
187
- click.echo()
188
-
189
- if em_formacao.direction == "up":
190
- simbolo = BRICK_UP
191
- else:
192
- simbolo = BRICK_DOWN
193
-
194
- linha = (
195
- f"FORMANDO {simbolo} "
196
- f"{em_formacao.open:.{DIGITS}f} "
197
- f"{em_formacao.close:.{DIGITS}f}"
198
- )
199
-
200
- click.echo(linha)
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)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli-renko"
3
- version = "1.1.2"
3
+ version = "1.2.0.dev1"
4
4
  description = "Renko plugin institucional para mtcli (MetaTrader 5)"
5
5
  authors = [
6
6
  { name = "Valmir França", email = "vfranca3@gmail.com" }
File without changes
File without changes