mtcli-renko 1.1.0.dev0__tar.gz → 1.1.0.dev2__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 (19) hide show
  1. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/PKG-INFO +1 -1
  2. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/commands/renko.py +12 -4
  3. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/conf.py +5 -0
  4. mtcli_renko-1.1.0.dev2/mtcli_renko/controllers/renko_controller.py +105 -0
  5. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/models/renko_model.py +58 -99
  6. mtcli_renko-1.1.0.dev2/mtcli_renko/views/renko_view.py +75 -0
  7. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/pyproject.toml +1 -1
  8. mtcli_renko-1.1.0.dev0/mtcli_renko/controllers/renko_controller.py +0 -66
  9. mtcli_renko-1.1.0.dev0/mtcli_renko/views/renko_view.py +0 -37
  10. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/LICENSE +0 -0
  11. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/README.md +0 -0
  12. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/__init__.py +0 -0
  13. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/commands/__init__.py +0 -0
  14. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/controllers/__init__.py +0 -0
  15. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/domain/__init__.py +0 -0
  16. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/domain/timeframe.py +0 -0
  17. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/models/__init__.py +0 -0
  18. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/mtcli_renko/plugin.py +0 -0
  19. {mtcli_renko-1.1.0.dev0 → mtcli_renko-1.1.0.dev2}/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.0.dev0
3
+ Version: 1.1.0.dev2
4
4
  Summary: Renko plugin institucional para mtcli (MetaTrader 5)
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -8,7 +8,7 @@ from ..controllers.renko_controller import RenkoController
8
8
  from ..views.renko_view import exibir_renko
9
9
  from ..domain.timeframe import Timeframe
10
10
  from mtcli.logger import setup_logger
11
- from ..conf import SYMBOL, BRICK, PERIOD, BARS, DATA_MODE, MAX_TICKS
11
+ from ..conf import SYMBOL, BRICK, PERIOD, BARS, DATA_MODE, MAX_TICKS, TICK_STYLE
12
12
 
13
13
  log = setup_logger(__name__)
14
14
 
@@ -43,7 +43,13 @@ log = setup_logger(__name__)
43
43
  default=MAX_TICKS,
44
44
  show_default=True,
45
45
  type=int,
46
- help="Limite máximo de ticks processados no modo tick.",
46
+ )
47
+ @click.option(
48
+ "--tick-style",
49
+ type=click.Choice(["estrutural", "hibrido", "agressivo"], case_sensitive=False),
50
+ default=TICK_STYLE,
51
+ show_default=True,
52
+ help="Define como tratar brick em formação no modo tick.",
47
53
  )
48
54
  def renko(
49
55
  symbol,
@@ -55,6 +61,7 @@ def renko(
55
61
  ancorar_abertura,
56
62
  data_mode,
57
63
  max_ticks,
64
+ tick_style,
58
65
  ):
59
66
 
60
67
  try:
@@ -71,7 +78,8 @@ def renko(
71
78
  ancorar_abertura,
72
79
  data_mode,
73
80
  max_ticks,
81
+ tick_style,
74
82
  )
75
83
 
76
- bricks = controller.executar()
77
- exibir_renko(bricks, numerar=numerar)
84
+ resultado = controller.executar()
85
+ exibir_renko(resultado, numerar=numerar)
@@ -87,3 +87,8 @@ MAX_TICKS = int(os.getenv(
87
87
  _get_config_value("RENKO", "max_ticks", 10000)
88
88
  ))
89
89
 
90
+ TICK_STYLE = os.getenv(
91
+ "TICK_STYLE",
92
+ _get_config_value("RENKO", "tick_style", "hibrido")
93
+ )
94
+
@@ -0,0 +1,105 @@
1
+ """
2
+ Renko controller.
3
+
4
+ Responsável por:
5
+ - Orquestrar obtenção de dados (candle ou tick)
6
+ - Chamar o model
7
+ - Aplicar estilo de saída no modo tick
8
+ """
9
+
10
+ from ..models.renko_model import RenkoModel
11
+ from mtcli.logger import setup_logger
12
+
13
+ log = setup_logger(__name__)
14
+
15
+
16
+ class RenkoController:
17
+ """
18
+ Controller principal do Renko.
19
+
20
+ :param symbol: ativo (ex: WINJ26)
21
+ :param brick_size: tamanho do brick
22
+ :param timeframe: timeframe MT5 (para candle)
23
+ :param quantidade: número de candles
24
+ :param modo: simples ou classico
25
+ :param ancorar_abertura: ancora na abertura da sessão
26
+ :param data_mode: candle ou tick
27
+ :param max_ticks: quantidade máxima de ticks
28
+ :param tick_style: estrutural | hibrido | agressivo
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ symbol,
34
+ brick_size,
35
+ timeframe,
36
+ quantidade,
37
+ modo="simples",
38
+ ancorar_abertura=False,
39
+ data_mode="candle",
40
+ max_ticks=3000,
41
+ tick_style="hibrido",
42
+ ):
43
+ self.model = RenkoModel(symbol, brick_size)
44
+ self.timeframe = timeframe
45
+ self.quantidade = quantidade
46
+ self.modo = modo
47
+ self.ancorar_abertura = ancorar_abertura
48
+ self.data_mode = data_mode
49
+ self.max_ticks = max_ticks
50
+ self.tick_style = tick_style
51
+
52
+ def executar(self):
53
+ """
54
+ Executa construção do Renko conforme modo configurado.
55
+ """
56
+
57
+ # =========================
58
+ # MODO TICK
59
+ # =========================
60
+ if self.data_mode == "tick":
61
+
62
+ ticks = self.model.obter_ticks(
63
+ timeframe=self.timeframe,
64
+ max_ticks=self.max_ticks,
65
+ )
66
+
67
+ # 🔒 Correção definitiva do erro numpy
68
+ if ticks is None or len(ticks) == 0:
69
+ return []
70
+
71
+ resultado = self.model.construir_renko_ticks(ticks)
72
+
73
+ # =========================
74
+ # Aplicar estilo de tick
75
+ # =========================
76
+
77
+ # Estrutural → somente confirmados
78
+ if self.tick_style == "estrutural":
79
+ return resultado.confirmados
80
+
81
+ # Agressivo → confirmados + parcial como válido
82
+ if self.tick_style == "agressivo":
83
+ if resultado.em_formacao:
84
+ return resultado.confirmados + [resultado.em_formacao]
85
+ return resultado.confirmados
86
+
87
+ # Híbrido (default) → retorna objeto completo
88
+ return resultado
89
+
90
+ # =========================
91
+ # MODO CANDLE
92
+ # =========================
93
+ rates = self.model.obter_rates(
94
+ self.timeframe,
95
+ self.quantidade,
96
+ ancorar_abertura=self.ancorar_abertura,
97
+ )
98
+
99
+ if rates is None or len(rates) == 0:
100
+ return []
101
+
102
+ return self.model.construir_renko(
103
+ rates,
104
+ modo=self.modo,
105
+ )
@@ -1,16 +1,15 @@
1
1
  """
2
2
  Renko model institucional profissional.
3
3
 
4
- Ancoragem determinística por data
5
- Suporte candle e tick
4
+ Candle mode determinístico
5
+ Tick mode híbrido (confirmados + em formação)
6
+ ✔ Estrutura estável
6
7
  ✔ Compatível com controller atual
7
- ✔ Seguro para numpy arrays
8
- ✔ Funciona fora do pregão
9
8
  """
10
9
 
11
10
  from dataclasses import dataclass
12
- from typing import List, Optional
13
- from datetime import datetime, timedelta
11
+ from typing import List, Optional, NamedTuple
12
+ from datetime import datetime
14
13
 
15
14
  import MetaTrader5 as mt5
16
15
 
@@ -22,7 +21,7 @@ log = setup_logger(__name__)
22
21
 
23
22
 
24
23
  # ==========================================================
25
- # DATA STRUCTURE
24
+ # DATA STRUCTURES
26
25
  # ==========================================================
27
26
 
28
27
  @dataclass
@@ -32,6 +31,11 @@ class RenkoBrick:
32
31
  close: float
33
32
 
34
33
 
34
+ class RenkoResult(NamedTuple):
35
+ confirmados: List[RenkoBrick]
36
+ em_formacao: Optional[RenkoBrick]
37
+
38
+
35
39
  # ==========================================================
36
40
  # MODEL
37
41
  # ==========================================================
@@ -43,7 +47,7 @@ class RenkoModel:
43
47
  self.brick_size = brick_size
44
48
 
45
49
  # ======================================================
46
- # AUXILIAR: descobrir último pregão real
50
+ # AUXILIAR
47
51
  # ======================================================
48
52
 
49
53
  def _ultimo_pregao_data(self, timeframe):
@@ -72,10 +76,6 @@ class RenkoModel:
72
76
  if not mt5.symbol_select(self.symbol, True):
73
77
  raise RuntimeError(f"Erro ao selecionar símbolo {self.symbol}")
74
78
 
75
- # -------------------------------------------------
76
- # SEM ANCORAGEM
77
- # -------------------------------------------------
78
-
79
79
  if not ancorar_abertura:
80
80
 
81
81
  if quantidade == 0:
@@ -88,14 +88,7 @@ class RenkoModel:
88
88
  quantidade,
89
89
  )
90
90
 
91
- if rates is None:
92
- return []
93
-
94
- return rates
95
-
96
- # -------------------------------------------------
97
- # COM ANCORAGEM REAL POR DATA
98
- # -------------------------------------------------
91
+ return rates or []
99
92
 
100
93
  data_pregao = self._ultimo_pregao_data(timeframe)
101
94
 
@@ -119,21 +112,13 @@ class RenkoModel:
119
112
  if r_time.date() == data_pregao:
120
113
  filtrado.append(r)
121
114
 
122
- if not filtrado:
123
- return []
124
-
125
- total = len(filtrado)
126
-
127
115
  if quantidade == 0:
128
116
  return filtrado
129
117
 
130
- if quantidade >= total:
131
- return filtrado
132
-
133
118
  return filtrado[-quantidade:]
134
119
 
135
120
  # ======================================================
136
- # TICKS (tick mode)
121
+ # TICKS
137
122
  # ======================================================
138
123
 
139
124
  def obter_ticks(self, timeframe, max_ticks=5000):
@@ -153,110 +138,62 @@ class RenkoModel:
153
138
  datetime.strptime(SESSION_OPEN, "%H:%M").time(),
154
139
  )
155
140
 
156
- ticks = mt5.copy_ticks_from(
141
+ agora = datetime.now()
142
+
143
+ ticks = mt5.copy_ticks_range(
157
144
  self.symbol,
158
145
  inicio,
159
- max_ticks,
146
+ agora,
160
147
  mt5.COPY_TICKS_ALL,
161
148
  )
162
149
 
163
- if ticks is None:
150
+ if ticks is None or len(ticks) == 0:
164
151
  return []
165
152
 
153
+ if len(ticks) > max_ticks:
154
+ ticks = ticks[-max_ticks:]
155
+
166
156
  return ticks
167
157
 
168
158
  # ======================================================
169
159
  # CONSTRUÇÃO RENKO (CANDLE)
170
160
  # ======================================================
171
161
 
172
- def construir_renko(
173
- self,
174
- rates,
175
- modo="simples",
176
- ) -> List[RenkoBrick]:
162
+ def construir_renko(self, rates, modo="simples") -> List[RenkoBrick]:
177
163
 
178
164
  if rates is None or len(rates) < 2:
179
165
  return []
180
166
 
181
167
  bricks: List[RenkoBrick] = []
182
-
183
168
  last_price = float(rates[0]["open"])
184
- direction: Optional[str] = None
185
169
 
186
170
  for rate in rates[1:]:
187
171
 
188
172
  high = float(rate["high"])
189
173
  low = float(rate["low"])
190
174
 
191
- # -------------------------------------------------
192
- # MODO SIMPLES
193
- # -------------------------------------------------
194
-
195
- if modo == "simples":
196
-
197
- while high - last_price >= self.brick_size:
198
- novo = last_price + self.brick_size
199
- bricks.append(RenkoBrick("up", last_price, novo))
200
- last_price = novo
201
-
202
- while last_price - low >= self.brick_size:
203
- novo = last_price - self.brick_size
204
- bricks.append(RenkoBrick("down", last_price, novo))
205
- last_price = novo
206
-
207
- # -------------------------------------------------
208
- # MODO CLÁSSICO (reversão 2x)
209
- # -------------------------------------------------
210
-
211
- elif modo == "classico":
212
-
213
- if direction in (None, "up"):
214
-
215
- while high - last_price >= self.brick_size:
216
- novo = last_price + self.brick_size
217
- bricks.append(RenkoBrick("up", last_price, novo))
218
- last_price = novo
219
- direction = "up"
220
-
221
- if (
222
- direction == "up"
223
- and last_price - low >= 2 * self.brick_size
224
- ):
225
- novo = last_price - self.brick_size
226
- bricks.append(RenkoBrick("down", last_price, novo))
227
- last_price = novo
228
- direction = "down"
229
-
230
- if direction in (None, "down"):
231
-
232
- while last_price - low >= self.brick_size:
233
- novo = last_price - self.brick_size
234
- bricks.append(RenkoBrick("down", last_price, novo))
235
- last_price = novo
236
- direction = "down"
237
-
238
- if (
239
- direction == "down"
240
- and high - last_price >= 2 * self.brick_size
241
- ):
242
- novo = last_price + self.brick_size
243
- bricks.append(RenkoBrick("up", last_price, novo))
244
- last_price = novo
245
- direction = "up"
175
+ while high - last_price >= self.brick_size:
176
+ novo = last_price + self.brick_size
177
+ bricks.append(RenkoBrick("up", last_price, novo))
178
+ last_price = novo
179
+
180
+ while last_price - low >= self.brick_size:
181
+ novo = last_price - self.brick_size
182
+ bricks.append(RenkoBrick("down", last_price, novo))
183
+ last_price = novo
246
184
 
247
185
  return bricks
248
186
 
249
187
  # ======================================================
250
- # CONSTRUÇÃO RENKO (TICK)
188
+ # CONSTRUÇÃO RENKO (TICK HÍBRIDO)
251
189
  # ======================================================
252
190
 
253
- def construir_renko_ticks(self, ticks) -> List[RenkoBrick]:
191
+ def construir_renko_ticks(self, ticks) -> RenkoResult:
254
192
 
255
193
  if ticks is None or len(ticks) < 2:
256
- return []
194
+ return RenkoResult([], None)
257
195
 
258
196
  bricks: List[RenkoBrick] = []
259
-
260
197
  last_price = float(ticks[0]["last"])
261
198
 
262
199
  for tick in ticks[1:]:
@@ -273,4 +210,26 @@ class RenkoModel:
273
210
  bricks.append(RenkoBrick("down", last_price, novo))
274
211
  last_price = novo
275
212
 
276
- return bricks
213
+ # ----------------------------
214
+ # Brick em formação
215
+ # ----------------------------
216
+
217
+ ultimo_preco = float(ticks[-1]["last"])
218
+ diferenca = ultimo_preco - last_price
219
+
220
+ em_formacao = None
221
+
222
+ if abs(diferenca) > 0:
223
+
224
+ direcao = "up" if diferenca > 0 else "down"
225
+
226
+ em_formacao = RenkoBrick(
227
+ direction=direcao,
228
+ open=last_price,
229
+ close=ultimo_preco,
230
+ )
231
+
232
+ return RenkoResult(
233
+ confirmados=bricks,
234
+ em_formacao=em_formacao,
235
+ )
@@ -0,0 +1,75 @@
1
+ """
2
+ Renko view acessível.
3
+ """
4
+
5
+ import click
6
+ from ..conf import DIGITS
7
+
8
+
9
+ def exibir_renko(resultado, numerar: bool = False):
10
+
11
+ if not resultado:
12
+ click.echo("Nenhum bloco Renko gerado.")
13
+ return
14
+
15
+ # Lista simples (estrutural ou agressivo)
16
+ if isinstance(resultado, list):
17
+
18
+ click.echo("=== GRAFICO RENKO ===")
19
+ click.echo(f"Total de blocos: {len(resultado)}")
20
+ click.echo()
21
+
22
+ for i, brick in enumerate(resultado, start=1):
23
+
24
+ if numerar:
25
+ linha = (
26
+ f"{i} "
27
+ f"{brick.direction.upper()} "
28
+ f"{brick.open:.{DIGITS}f} "
29
+ f"{brick.close:.{DIGITS}f}"
30
+ )
31
+ else:
32
+ linha = (
33
+ f"{brick.direction.upper()} "
34
+ f"{brick.open:.{DIGITS}f} "
35
+ f"{brick.close:.{DIGITS}f}"
36
+ )
37
+
38
+ click.echo(linha)
39
+
40
+ return
41
+
42
+ # Híbrido
43
+ confirmados = resultado.confirmados
44
+ em_formacao = resultado.em_formacao
45
+
46
+ click.echo("=== GRAFICO RENKO ===")
47
+ click.echo(f"Blocos confirmados: {len(confirmados)}")
48
+ click.echo()
49
+
50
+ for i, brick in enumerate(confirmados, start=1):
51
+
52
+ if numerar:
53
+ linha = (
54
+ f"{i} "
55
+ f"{brick.direction.upper()} "
56
+ f"{brick.open:.{DIGITS}f} "
57
+ f"{brick.close:.{DIGITS}f}"
58
+ )
59
+ else:
60
+ linha = (
61
+ f"{brick.direction.upper()} "
62
+ f"{brick.open:.{DIGITS}f} "
63
+ f"{brick.close:.{DIGITS}f}"
64
+ )
65
+
66
+ click.echo(linha)
67
+
68
+ if em_formacao:
69
+ click.echo()
70
+ click.echo("EM FORMACAO:")
71
+ click.echo(
72
+ f"{em_formacao.direction.upper()} "
73
+ f"{em_formacao.open:.{DIGITS}f} "
74
+ f"{em_formacao.close:.{DIGITS}f}"
75
+ )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli-renko"
3
- version = "1.1.0.dev0"
3
+ version = "1.1.0.dev2"
4
4
  description = "Renko plugin institucional para mtcli (MetaTrader 5)"
5
5
  authors = [
6
6
  { name = "Valmir França", email = "vfranca3@gmail.com" }
@@ -1,66 +0,0 @@
1
- """
2
- Renko controller.
3
- """
4
-
5
- from ..models.renko_model import RenkoModel
6
- from mtcli.logger import setup_logger
7
-
8
- log = setup_logger(__name__)
9
-
10
-
11
- class RenkoController:
12
-
13
- def __init__(
14
- self,
15
- symbol,
16
- brick_size,
17
- timeframe,
18
- quantidade,
19
- modo="simples",
20
- ancorar_abertura=False,
21
- data_mode="candle",
22
- max_ticks=3000,
23
- ):
24
- self.model = RenkoModel(symbol, brick_size)
25
- self.timeframe = timeframe
26
- self.quantidade = quantidade
27
- self.modo = modo
28
- self.ancorar_abertura = ancorar_abertura
29
- self.data_mode = data_mode
30
- self.max_ticks = max_ticks
31
-
32
- def executar(self):
33
-
34
- # -------------------------------------------------
35
- # TICK MODE
36
- # -------------------------------------------------
37
-
38
- if self.data_mode == "tick":
39
-
40
- ticks = self.model.obter_ticks(
41
- timeframe=self.timeframe,
42
- max_ticks=self.max_ticks,
43
- )
44
-
45
- if ticks is None or len(ticks) == 0:
46
- return []
47
-
48
- return self.model.construir_renko_ticks(ticks)
49
-
50
- # -------------------------------------------------
51
- # CANDLE MODE
52
- # -------------------------------------------------
53
-
54
- rates = self.model.obter_rates(
55
- self.timeframe,
56
- self.quantidade,
57
- ancorar_abertura=self.ancorar_abertura,
58
- )
59
-
60
- if rates is None or len(rates) == 0:
61
- return []
62
-
63
- return self.model.construir_renko(
64
- rates,
65
- modo=self.modo,
66
- )
@@ -1,37 +0,0 @@
1
- """
2
- Renko view.
3
-
4
- Saída textual acessível para leitores de tela.
5
- """
6
-
7
- import click
8
- from ..conf import DIGITS
9
-
10
-
11
- def exibir_renko(bricks, numerar: bool = False):
12
-
13
- if not bricks:
14
- click.echo("Nenhum bloco Renko gerado.")
15
- return
16
-
17
- click.echo("=== GRAFICO RENKO ===")
18
- click.echo(f"Total de blocos: {len(bricks)}")
19
- click.echo()
20
-
21
- for i, brick in enumerate(bricks, start=1):
22
-
23
- if numerar:
24
- linha = (
25
- f"{i} "
26
- f"{brick.direction.upper()} "
27
- f"{brick.open:.{DIGITS}f} "
28
- f"{brick.close:.{DIGITS}f}"
29
- )
30
- else:
31
- linha = (
32
- f"{brick.direction.upper()} "
33
- f"{brick.open:.{DIGITS}f} "
34
- f"{brick.close:.{DIGITS}f}"
35
- )
36
-
37
- click.echo(linha)