mtcli-renko 1.1.0.dev1__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.dev1 → mtcli_renko-1.1.0.dev2}/PKG-INFO +1 -1
  2. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/commands/renko.py +12 -4
  3. {mtcli_renko-1.1.0.dev1 → 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.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/models/renko_model.py +47 -71
  6. mtcli_renko-1.1.0.dev2/mtcli_renko/views/renko_view.py +75 -0
  7. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/pyproject.toml +1 -1
  8. mtcli_renko-1.1.0.dev1/mtcli_renko/controllers/renko_controller.py +0 -66
  9. mtcli_renko-1.1.0.dev1/mtcli_renko/views/renko_view.py +0 -37
  10. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/LICENSE +0 -0
  11. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/README.md +0 -0
  12. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/__init__.py +0 -0
  13. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/commands/__init__.py +0 -0
  14. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/controllers/__init__.py +0 -0
  15. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/domain/__init__.py +0 -0
  16. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/domain/timeframe.py +0 -0
  17. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/models/__init__.py +0 -0
  18. {mtcli_renko-1.1.0.dev1 → mtcli_renko-1.1.0.dev2}/mtcli_renko/plugin.py +0 -0
  19. {mtcli_renko-1.1.0.dev1 → 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.dev1
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,15 +1,14 @@
1
1
  """
2
2
  Renko model institucional profissional.
3
3
 
4
- Ancoragem determinística por data (candle mode)
5
- ✔ Tick mode com janela móvel até o momento atual
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 em mercado aberto e fechado
9
8
  """
10
9
 
11
10
  from dataclasses import dataclass
12
- from typing import List, Optional
11
+ from typing import List, Optional, NamedTuple
13
12
  from datetime import datetime
14
13
 
15
14
  import MetaTrader5 as mt5
@@ -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
  # ==========================================================
@@ -86,10 +90,6 @@ class RenkoModel:
86
90
 
87
91
  return rates or []
88
92
 
89
- # -------------------------
90
- # Ancorado no último pregão
91
- # -------------------------
92
-
93
93
  data_pregao = self._ultimo_pregao_data(timeframe)
94
94
 
95
95
  if data_pregao is None:
@@ -118,7 +118,7 @@ class RenkoModel:
118
118
  return filtrado[-quantidade:]
119
119
 
120
120
  # ======================================================
121
- # TICKS (tick mode corrigido)
121
+ # TICKS
122
122
  # ======================================================
123
123
 
124
124
  def obter_ticks(self, timeframe, max_ticks=5000):
@@ -150,7 +150,6 @@ class RenkoModel:
150
150
  if ticks is None or len(ticks) == 0:
151
151
  return []
152
152
 
153
- # 🔥 Pegar apenas os ticks mais recentes
154
153
  if len(ticks) > max_ticks:
155
154
  ticks = ticks[-max_ticks:]
156
155
 
@@ -160,86 +159,41 @@ class RenkoModel:
160
159
  # CONSTRUÇÃO RENKO (CANDLE)
161
160
  # ======================================================
162
161
 
163
- def construir_renko(
164
- self,
165
- rates,
166
- modo="simples",
167
- ) -> List[RenkoBrick]:
162
+ def construir_renko(self, rates, modo="simples") -> List[RenkoBrick]:
168
163
 
169
164
  if rates is None or len(rates) < 2:
170
165
  return []
171
166
 
172
167
  bricks: List[RenkoBrick] = []
173
-
174
168
  last_price = float(rates[0]["open"])
175
- direction: Optional[str] = None
176
169
 
177
170
  for rate in rates[1:]:
178
171
 
179
172
  high = float(rate["high"])
180
173
  low = float(rate["low"])
181
174
 
182
- if modo == "simples":
183
-
184
- while high - last_price >= self.brick_size:
185
- novo = last_price + self.brick_size
186
- bricks.append(RenkoBrick("up", last_price, novo))
187
- last_price = novo
188
-
189
- while last_price - low >= self.brick_size:
190
- novo = last_price - self.brick_size
191
- bricks.append(RenkoBrick("down", last_price, novo))
192
- last_price = novo
193
-
194
- elif modo == "classico":
195
-
196
- if direction in (None, "up"):
197
-
198
- while high - last_price >= self.brick_size:
199
- novo = last_price + self.brick_size
200
- bricks.append(RenkoBrick("up", last_price, novo))
201
- last_price = novo
202
- direction = "up"
203
-
204
- if (
205
- direction == "up"
206
- and last_price - low >= 2 * self.brick_size
207
- ):
208
- novo = last_price - self.brick_size
209
- bricks.append(RenkoBrick("down", last_price, novo))
210
- last_price = novo
211
- direction = "down"
212
-
213
- if direction in (None, "down"):
214
-
215
- while last_price - low >= self.brick_size:
216
- novo = last_price - self.brick_size
217
- bricks.append(RenkoBrick("down", last_price, novo))
218
- last_price = novo
219
- direction = "down"
220
-
221
- if (
222
- direction == "down"
223
- and high - last_price >= 2 * self.brick_size
224
- ):
225
- novo = last_price + self.brick_size
226
- bricks.append(RenkoBrick("up", last_price, novo))
227
- last_price = novo
228
- 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
229
184
 
230
185
  return bricks
231
186
 
232
187
  # ======================================================
233
- # CONSTRUÇÃO RENKO (TICK)
188
+ # CONSTRUÇÃO RENKO (TICK HÍBRIDO)
234
189
  # ======================================================
235
190
 
236
- def construir_renko_ticks(self, ticks, modo="simples") -> List[RenkoBrick]:
191
+ def construir_renko_ticks(self, ticks) -> RenkoResult:
237
192
 
238
193
  if ticks is None or len(ticks) < 2:
239
- return []
194
+ return RenkoResult([], None)
240
195
 
241
196
  bricks: List[RenkoBrick] = []
242
-
243
197
  last_price = float(ticks[0]["last"])
244
198
 
245
199
  for tick in ticks[1:]:
@@ -256,4 +210,26 @@ class RenkoModel:
256
210
  bricks.append(RenkoBrick("down", last_price, novo))
257
211
  last_price = novo
258
212
 
259
- 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.dev1"
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)