mtcli-renko 1.0.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Valmir França
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtcli-renko
3
+ Version: 1.0.0
4
+ Summary: Renko plugin institucional para mtcli (MetaTrader 5)
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: trading,renko,metatrader5,mt5,cli,price-action
8
+ Author: Valmir França
9
+ Author-email: vfranca3@gmail.com
10
+ Requires-Python: >=3.10,<3.14
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Office/Business :: Financial :: Investment
20
+ Requires-Dist: click (>=8.3.0,<9.0.0)
21
+ Requires-Dist: metatrader5 (>=5.0.5370,<6.0.0)
22
+ Requires-Dist: mtcli (>=3.2.0)
23
+ Project-URL: Documentation, https://vfranca.github.io/mtcli-renko
24
+ Project-URL: Homepage, https://github.com/vfranca/mtcli-renko
25
+ Project-URL: Issues, https://github.com/vfranca/mtcli-renko/issues
26
+ Project-URL: Repository, https://github.com/vfranca/mtcli-renko
27
+ Description-Content-Type: text/markdown
28
+
29
+ # mtcli-renko
30
+
31
+ Renko institucional para MetaTrader 5 integrado ao ecossistema `mtcli`.
32
+
33
+ Geração de gráfico Renko em modo texto (terminal), com:
34
+
35
+ - Renko simples
36
+ - Renko clássico (reversão 2x)
37
+ - Ancoragem real no último pregão
38
+ - Controle de barras por sessão
39
+ - Compatível com B3, Forex e ativos 24h
40
+ - Ideal para uso via CLI e automação
41
+
42
+ ---
43
+
44
+ ## Instalação
45
+
46
+ Via pip:
47
+
48
+ ```bash
49
+ pip install mtcli-renko
50
+ ````
51
+
52
+ Ou via Poetry:
53
+
54
+ ```bash
55
+ poetry add mtcli-renko
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Requisitos
61
+
62
+ * Python 3.10+
63
+ * MetaTrader 5 instalado
64
+ * Conta conectada ao terminal MT5
65
+ * Plugin `mtcli` configurado
66
+
67
+ ---
68
+
69
+ ## Uso
70
+
71
+ Após instalar, o comando fica disponível dentro do `mt`:
72
+
73
+ ```bash
74
+ mt renko --symbol WINJ26 --brick 50
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Parâmetros
80
+
81
+ | Opção | Descrição |
82
+ | -------------------- | --------------------------------- |
83
+ | `--symbol`, `-s` | Ativo (ex: WINJ26) |
84
+ | `--brick`, `-b` | Tamanho do brick |
85
+ | `--timeframe`, `-t` | Timeframe (m1, m5, m15, h1, etc.) |
86
+ | `--bars`, `-n` | Quantidade de barras base |
87
+ | `--modo` | `simples` ou `classico` |
88
+ | `--ancorar-abertura` | Ancora no último pregão |
89
+
90
+ ---
91
+
92
+ ## Exemplos
93
+
94
+ ### Renko simples padrão
95
+
96
+ ```bash
97
+ mt renko -s WINJ26 -b 50
98
+ ```
99
+
100
+ ### Renko clássico (reversão 2x)
101
+
102
+ ```bash
103
+ mt renko -s WINJ26 -b 50 --modo classico
104
+ ```
105
+
106
+ ### Ancorado no último pregão
107
+
108
+ ```bash
109
+ mt renko -s WINJ26 -b 50 --ancorar-abertura
110
+ ```
111
+
112
+ ### Todas as barras do último pregão
113
+
114
+ ```bash
115
+ mt renko -s WINJ26 -b 50 --ancorar-abertura --bars 0
116
+ ```
117
+
118
+ ### Últimas 20 barras do último pregão
119
+
120
+ ```bash
121
+ mt renko -s WINJ26 -b 50 --ancorar-abertura --bars 20
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Timeframes aceitos
127
+
128
+ Use valores simplificados:
129
+
130
+ * m1
131
+ * m5
132
+ * m15
133
+ * m30
134
+ * h1
135
+ * h4
136
+ * d1
137
+
138
+ O sistema faz o mapeamento automático para as constantes do MetaTrader 5.
139
+
140
+ ---
141
+
142
+ ## Ancoragem Institucional
143
+
144
+ Quando `--ancorar-abertura` é ativado:
145
+
146
+ * Detecta o último candle disponível
147
+ * Descobre a data do último pregão real
148
+ * Filtra manualmente apenas aquele dia
149
+ * Ignora histórico anterior
150
+ * Funciona inclusive em domingos e feriados
151
+
152
+ Comportamento:
153
+
154
+ * `--bars 0` → todas as barras do último pregão
155
+ * `--bars N` → últimas N barras daquele pregão
156
+
157
+ ---
158
+
159
+ ## Modos de Construção
160
+
161
+ ### Simples
162
+
163
+ Cria bricks contínuos sem regra de reversão 2x.
164
+
165
+ ### Clássico
166
+
167
+ Implementa reversão apenas quando o preço move 2x o tamanho do brick na direção oposta.
168
+
169
+ ---
170
+
171
+ ## Estrutura do Projeto
172
+
173
+ ```
174
+ mtcli_renko/
175
+
176
+ ├── commands/
177
+ ├── controllers/
178
+ ├── models/
179
+ ├── views/
180
+ ├── conf.py
181
+ └── enums.py
182
+ ```
183
+
184
+ Arquitetura baseada em MVC, alinhada ao padrão do `mtcli-trade`.
185
+
186
+ ---
187
+
188
+ ## Casos de Uso
189
+
190
+ * Leitura de estrutura (H1, H2, H3, L1, L2)
191
+ * Identificação de BRF / BLF
192
+ * Automação de análise
193
+ * Backtesting via script
194
+ * Operação institucional via terminal
195
+
196
+ ---
197
+
198
+ ## Compatibilidade
199
+
200
+ * B3 (ex: WIN, WDO)
201
+ * Forex
202
+ * Cripto
203
+ * Ativos 24h
204
+
205
+ ---
206
+
207
+ ## Roadmap
208
+
209
+ * [ ] Múltiplas sessões
210
+ * [ ] Filtro de horário (09:00–18:00)
211
+ * [ ] VWAP integrada
212
+ * [ ] Detecção automática de estrutura (H1/H2/L2)
213
+ * [ ] Exportação CSV
214
+
215
+ ---
216
+
217
+ ## Licença
218
+
219
+ MIT License
220
+
221
+ ---
222
+
223
+ ## Autor
224
+
225
+ Valmir França
226
+
227
+ ---
228
+
229
+ ## Contribuição
230
+
231
+ Pull requests são bem-vindos.
232
+ Para mudanças maiores, abra uma issue antes para discussão.
233
+
234
+ ---
235
+
236
+ ## Aviso
237
+
238
+ Este software não constitui recomendação de investimento.
239
+ Uso por conta e risco do operador.
240
+
@@ -0,0 +1,211 @@
1
+ # mtcli-renko
2
+
3
+ Renko institucional para MetaTrader 5 integrado ao ecossistema `mtcli`.
4
+
5
+ Geração de gráfico Renko em modo texto (terminal), com:
6
+
7
+ - Renko simples
8
+ - Renko clássico (reversão 2x)
9
+ - Ancoragem real no último pregão
10
+ - Controle de barras por sessão
11
+ - Compatível com B3, Forex e ativos 24h
12
+ - Ideal para uso via CLI e automação
13
+
14
+ ---
15
+
16
+ ## Instalação
17
+
18
+ Via pip:
19
+
20
+ ```bash
21
+ pip install mtcli-renko
22
+ ````
23
+
24
+ Ou via Poetry:
25
+
26
+ ```bash
27
+ poetry add mtcli-renko
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Requisitos
33
+
34
+ * Python 3.10+
35
+ * MetaTrader 5 instalado
36
+ * Conta conectada ao terminal MT5
37
+ * Plugin `mtcli` configurado
38
+
39
+ ---
40
+
41
+ ## Uso
42
+
43
+ Após instalar, o comando fica disponível dentro do `mt`:
44
+
45
+ ```bash
46
+ mt renko --symbol WINJ26 --brick 50
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Parâmetros
52
+
53
+ | Opção | Descrição |
54
+ | -------------------- | --------------------------------- |
55
+ | `--symbol`, `-s` | Ativo (ex: WINJ26) |
56
+ | `--brick`, `-b` | Tamanho do brick |
57
+ | `--timeframe`, `-t` | Timeframe (m1, m5, m15, h1, etc.) |
58
+ | `--bars`, `-n` | Quantidade de barras base |
59
+ | `--modo` | `simples` ou `classico` |
60
+ | `--ancorar-abertura` | Ancora no último pregão |
61
+
62
+ ---
63
+
64
+ ## Exemplos
65
+
66
+ ### Renko simples padrão
67
+
68
+ ```bash
69
+ mt renko -s WINJ26 -b 50
70
+ ```
71
+
72
+ ### Renko clássico (reversão 2x)
73
+
74
+ ```bash
75
+ mt renko -s WINJ26 -b 50 --modo classico
76
+ ```
77
+
78
+ ### Ancorado no último pregão
79
+
80
+ ```bash
81
+ mt renko -s WINJ26 -b 50 --ancorar-abertura
82
+ ```
83
+
84
+ ### Todas as barras do último pregão
85
+
86
+ ```bash
87
+ mt renko -s WINJ26 -b 50 --ancorar-abertura --bars 0
88
+ ```
89
+
90
+ ### Últimas 20 barras do último pregão
91
+
92
+ ```bash
93
+ mt renko -s WINJ26 -b 50 --ancorar-abertura --bars 20
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Timeframes aceitos
99
+
100
+ Use valores simplificados:
101
+
102
+ * m1
103
+ * m5
104
+ * m15
105
+ * m30
106
+ * h1
107
+ * h4
108
+ * d1
109
+
110
+ O sistema faz o mapeamento automático para as constantes do MetaTrader 5.
111
+
112
+ ---
113
+
114
+ ## Ancoragem Institucional
115
+
116
+ Quando `--ancorar-abertura` é ativado:
117
+
118
+ * Detecta o último candle disponível
119
+ * Descobre a data do último pregão real
120
+ * Filtra manualmente apenas aquele dia
121
+ * Ignora histórico anterior
122
+ * Funciona inclusive em domingos e feriados
123
+
124
+ Comportamento:
125
+
126
+ * `--bars 0` → todas as barras do último pregão
127
+ * `--bars N` → últimas N barras daquele pregão
128
+
129
+ ---
130
+
131
+ ## Modos de Construção
132
+
133
+ ### Simples
134
+
135
+ Cria bricks contínuos sem regra de reversão 2x.
136
+
137
+ ### Clássico
138
+
139
+ Implementa reversão apenas quando o preço move 2x o tamanho do brick na direção oposta.
140
+
141
+ ---
142
+
143
+ ## Estrutura do Projeto
144
+
145
+ ```
146
+ mtcli_renko/
147
+
148
+ ├── commands/
149
+ ├── controllers/
150
+ ├── models/
151
+ ├── views/
152
+ ├── conf.py
153
+ └── enums.py
154
+ ```
155
+
156
+ Arquitetura baseada em MVC, alinhada ao padrão do `mtcli-trade`.
157
+
158
+ ---
159
+
160
+ ## Casos de Uso
161
+
162
+ * Leitura de estrutura (H1, H2, H3, L1, L2)
163
+ * Identificação de BRF / BLF
164
+ * Automação de análise
165
+ * Backtesting via script
166
+ * Operação institucional via terminal
167
+
168
+ ---
169
+
170
+ ## Compatibilidade
171
+
172
+ * B3 (ex: WIN, WDO)
173
+ * Forex
174
+ * Cripto
175
+ * Ativos 24h
176
+
177
+ ---
178
+
179
+ ## Roadmap
180
+
181
+ * [ ] Múltiplas sessões
182
+ * [ ] Filtro de horário (09:00–18:00)
183
+ * [ ] VWAP integrada
184
+ * [ ] Detecção automática de estrutura (H1/H2/L2)
185
+ * [ ] Exportação CSV
186
+
187
+ ---
188
+
189
+ ## Licença
190
+
191
+ MIT License
192
+
193
+ ---
194
+
195
+ ## Autor
196
+
197
+ Valmir França
198
+
199
+ ---
200
+
201
+ ## Contribuição
202
+
203
+ Pull requests são bem-vindos.
204
+ Para mudanças maiores, abra uma issue antes para discussão.
205
+
206
+ ---
207
+
208
+ ## Aviso
209
+
210
+ Este software não constitui recomendação de investimento.
211
+ Uso por conta e risco do operador.
File without changes
File without changes
@@ -0,0 +1,52 @@
1
+ """
2
+ Comando CLI para geração de gráfico Renko.
3
+ """
4
+
5
+ import click
6
+
7
+ from ..controllers.renko_controller import RenkoController
8
+ from ..views.renko_view import exibir_renko
9
+ from ..domain.timeframe import Timeframe
10
+ from mtcli.logger import setup_logger
11
+ from ..conf import SYMBOL, BRICK, PERIOD, BARS
12
+
13
+ log = setup_logger(__name__)
14
+
15
+
16
+ @click.command()
17
+ @click.version_option(package_name="mtcli-renko")
18
+ @click.option("--symbol", "-s", default=SYMBOL, show_default=True)
19
+ @click.option("--brick", "-b", default=BRICK, show_default=True, type=float)
20
+ @click.option("--timeframe", "-t", default=PERIOD, show_default=True)
21
+ @click.option("--bars", "-n", default=BARS, show_default=True, type=int)
22
+ @click.option("--numerar/--no-numerar", default=False, show_default=True)
23
+ @click.option(
24
+ "--modo",
25
+ type=click.Choice(["simples", "classico"], case_sensitive=False),
26
+ default="simples",
27
+ show_default=True,
28
+ )
29
+ @click.option(
30
+ "--ancorar-abertura",
31
+ is_flag=True,
32
+ default=False,
33
+ show_default=True,
34
+ )
35
+ def renko(symbol, brick, timeframe, bars, numerar, modo, ancorar_abertura):
36
+
37
+ try:
38
+ tf_enum = Timeframe.from_string(timeframe)
39
+ except ValueError as e:
40
+ raise click.BadParameter(str(e))
41
+
42
+ controller = RenkoController(
43
+ symbol,
44
+ brick,
45
+ tf_enum.mt5_const,
46
+ bars,
47
+ modo,
48
+ ancorar_abertura,
49
+ )
50
+
51
+ bricks = controller.executar()
52
+ exibir_renko(bricks, numerar=numerar)
@@ -0,0 +1,78 @@
1
+ """
2
+ conf.py
3
+
4
+ Módulo de configuração do plugin mtcli-delta.
5
+
6
+ Responsável por:
7
+
8
+ - Ler configurações do arquivo mtcli.ini
9
+ - Utilizar a seção [RENKO]
10
+ - Permitir override por variáveis de ambiente
11
+ - Definir valores padrão seguros
12
+
13
+ Ordem de precedência:
14
+
15
+ 1. Variáveis de ambiente
16
+ 2. Seção [RENKO] do mtcli.ini
17
+ 3. Seção [DEFAULT] do mtcli.ini
18
+ 4. Valores padrão definidos no código
19
+ """
20
+
21
+ import os
22
+ from mtcli.conf import config
23
+
24
+
25
+ def _get_config_value(section: str, key: str, fallback):
26
+ """
27
+ Obtém valor do arquivo de configuração com fallback seguro.
28
+
29
+ Ordem:
30
+ - Se existir na seção informada
31
+ - Se existir em DEFAULT
32
+ - Caso contrário usa fallback
33
+
34
+ :param section: Nome da seção no ini.
35
+ :param key: Nome da chave.
36
+ :param fallback: Valor padrão final.
37
+ :return: Valor encontrado ou fallback.
38
+ """
39
+ if config.has_section(section) and key in config[section]:
40
+ return config[section].get(key, fallback=fallback)
41
+
42
+ return config["DEFAULT"].get(key, fallback=fallback)
43
+
44
+
45
+ # -------------------------------------------------------
46
+ # Leitura da seção [RENKO] do mtcli.ini
47
+ # -------------------------------------------------------
48
+
49
+ SYMBOL = os.getenv(
50
+ "SYMBOL",
51
+ _get_config_value("RENKO", "symbol", "WIN$N")
52
+ )
53
+
54
+ BRICK = float(os.getenv(
55
+ "BRICK",
56
+ _get_config_value("RENKO", "brick", 50)
57
+ ))
58
+
59
+ PERIOD = os.getenv(
60
+ "PERIOD",
61
+ _get_config_value("RENKO", "period", "M5")
62
+ )
63
+
64
+ BARS = int(os.getenv(
65
+ "BARS",
66
+ _get_config_value("RENKO", "bars", 500)
67
+ ))
68
+
69
+ DIGITS = int(os.getenv(
70
+ "DIGITS",
71
+ _get_config_value("RENKO", "digits", 2)
72
+ ))
73
+
74
+ # Hora oficial de abertura do pregão (HH:MM)
75
+ SESSION_OPEN = os.getenv(
76
+ "SESSION_OPEN",
77
+ _get_config_value("RENKO", "session_open", "09:00")
78
+ )
File without changes
@@ -0,0 +1,43 @@
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
+ ):
22
+ self.model = RenkoModel(symbol, brick_size)
23
+ self.timeframe = timeframe
24
+ self.quantidade = quantidade
25
+ self.modo = modo
26
+ self.ancorar_abertura = ancorar_abertura
27
+
28
+ def executar(self):
29
+
30
+ rates = self.model.obter_rates(
31
+ self.timeframe,
32
+ self.quantidade,
33
+ ancorar_abertura=self.ancorar_abertura,
34
+ )
35
+
36
+ if rates is None or len(rates) == 0:
37
+ return []
38
+
39
+ return self.model.construir_renko(
40
+ rates,
41
+ modo=self.modo,
42
+ ancorar_abertura=self.ancorar_abertura,
43
+ )
File without changes
@@ -0,0 +1,87 @@
1
+ """
2
+ Enum de Timeframes suportados pelo MTCLI Renko.
3
+
4
+ Fornece:
5
+ - Conversão amigável (m5, 5m, h1, 1h)
6
+ - Mapeamento para constante MT5
7
+ - Lista de valores válidos para CLI
8
+ """
9
+
10
+ from enum import Enum
11
+ import MetaTrader5 as mt5
12
+
13
+
14
+ class Timeframe(Enum):
15
+ """
16
+ Representa timeframes suportados pelo MT5.
17
+ """
18
+
19
+ M1 = ("m1", mt5.TIMEFRAME_M1)
20
+ M2 = ("m2", mt5.TIMEFRAME_M2)
21
+ M3 = ("m3", mt5.TIMEFRAME_M3)
22
+ M4 = ("m4", mt5.TIMEFRAME_M4)
23
+ M5 = ("m5", mt5.TIMEFRAME_M5)
24
+ M10 = ("m10", mt5.TIMEFRAME_M10)
25
+ M15 = ("m15", mt5.TIMEFRAME_M15)
26
+ M30 = ("m30", mt5.TIMEFRAME_M30)
27
+
28
+ H1 = ("h1", mt5.TIMEFRAME_H1)
29
+ H2 = ("h2", mt5.TIMEFRAME_H2)
30
+ H3 = ("h3", mt5.TIMEFRAME_H3)
31
+ H4 = ("h4", mt5.TIMEFRAME_H4)
32
+ H6 = ("h6", mt5.TIMEFRAME_H6)
33
+ H8 = ("h8", mt5.TIMEFRAME_H8)
34
+ H12 = ("h12", mt5.TIMEFRAME_H12)
35
+
36
+ D1 = ("d1", mt5.TIMEFRAME_D1)
37
+ W1 = ("w1", mt5.TIMEFRAME_W1)
38
+ MN1 = ("mn1", mt5.TIMEFRAME_MN1)
39
+
40
+ def __init__(self, label: str, mt5_const: int):
41
+ self.label = label
42
+ self.mt5_const = mt5_const
43
+
44
+ @classmethod
45
+ def from_string(cls, value: str) -> "Timeframe":
46
+ """
47
+ Converte string amigável para Enum Timeframe.
48
+
49
+ Aceita:
50
+ m5, 5m
51
+ h1, 1h
52
+ d1, 1d
53
+ """
54
+
55
+ value = value.strip().lower()
56
+
57
+ # Aliases humanos
58
+ aliases = {
59
+ "1m": "m1",
60
+ "5m": "m5",
61
+ "15m": "m15",
62
+ "30m": "m30",
63
+ "1h": "h1",
64
+ "4h": "h4",
65
+ "1d": "d1",
66
+ "1w": "w1",
67
+ "1mo": "mn1",
68
+ }
69
+
70
+ if value in aliases:
71
+ value = aliases[value]
72
+
73
+ for tf in cls:
74
+ if tf.label == value:
75
+ return tf
76
+
77
+ raise ValueError(
78
+ f"Timeframe inválido: {value}. "
79
+ f"Use: {', '.join(cls.valid_labels())}"
80
+ )
81
+
82
+ @classmethod
83
+ def valid_labels(cls):
84
+ """
85
+ Retorna lista de labels válidos.
86
+ """
87
+ return [tf.label for tf in cls]
File without changes
@@ -0,0 +1,227 @@
1
+ """
2
+ Renko model institucional.
3
+
4
+ Ancoragem determinística no último pregão real.
5
+ Nunca depende do filtro interno do MT5.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import List, Optional
10
+ from datetime import datetime
11
+
12
+ import MetaTrader5 as mt5
13
+
14
+ from mtcli.mt5_context import mt5_conexao
15
+ from mtcli.logger import setup_logger
16
+
17
+ log = setup_logger(__name__)
18
+
19
+
20
+ # ==========================================================
21
+ # DATA STRUCTURE
22
+ # ==========================================================
23
+
24
+ @dataclass
25
+ class RenkoBrick:
26
+ direction: str
27
+ open: float
28
+ close: float
29
+
30
+
31
+ # ==========================================================
32
+ # MODEL
33
+ # ==========================================================
34
+
35
+ class RenkoModel:
36
+
37
+ def __init__(self, symbol: str, brick_size: float):
38
+ self.symbol = symbol
39
+ self.brick_size = brick_size
40
+
41
+ # ======================================================
42
+ # OBTENÇÃO DE DADOS
43
+ # ======================================================
44
+
45
+ def obter_rates(self, timeframe, quantidade: int, ancorar_abertura=False):
46
+
47
+ with mt5_conexao():
48
+
49
+ if not mt5.symbol_select(self.symbol, True):
50
+ raise RuntimeError(f"Erro ao selecionar símbolo {self.symbol}")
51
+
52
+ # -------------------------------------------------
53
+ # SEM ANCORAGEM
54
+ # -------------------------------------------------
55
+ if not ancorar_abertura:
56
+
57
+ if quantidade == 0:
58
+ quantidade = 1000
59
+
60
+ return mt5.copy_rates_from_pos(
61
+ self.symbol,
62
+ timeframe,
63
+ 0,
64
+ quantidade,
65
+ )
66
+
67
+ # -------------------------------------------------
68
+ # COM ANCORAGEM PROFISSIONAL
69
+ # -------------------------------------------------
70
+
71
+ # 1️⃣ Descobrir último candle real disponível
72
+ ultimo = mt5.copy_rates_from_pos(
73
+ self.symbol,
74
+ timeframe,
75
+ 0,
76
+ 1,
77
+ )
78
+
79
+ if ultimo is None or len(ultimo) == 0:
80
+ return None
81
+
82
+ ultimo_time = datetime.fromtimestamp(ultimo[0]["time"])
83
+ data_referencia = ultimo_time.date()
84
+
85
+ log.info(f"[Renko] Último pregão detectado: {data_referencia}")
86
+
87
+ # 2️⃣ Buscar bloco recente grande
88
+ bruto = mt5.copy_rates_from_pos(
89
+ self.symbol,
90
+ timeframe,
91
+ 0,
92
+ 5000,
93
+ )
94
+
95
+ if bruto is None:
96
+ return None
97
+
98
+ # 3️⃣ FILTRAGEM MANUAL POR DATA
99
+ filtrado = []
100
+
101
+ for r in bruto:
102
+ r_time = datetime.fromtimestamp(r["time"])
103
+ if r_time.date() == data_referencia:
104
+ filtrado.append(r)
105
+
106
+ if not filtrado:
107
+ return None
108
+
109
+ total = len(filtrado)
110
+
111
+ log.info(f"[Renko] Barras no último pregão: {total}")
112
+
113
+ # 4️⃣ Aplicar regra de quantidade
114
+ if quantidade == 0:
115
+ return filtrado
116
+
117
+ if quantidade >= total:
118
+ return filtrado
119
+
120
+ return filtrado[-quantidade:]
121
+
122
+ # ======================================================
123
+ # CONSTRUÇÃO RENKO
124
+ # ======================================================
125
+
126
+ def construir_renko(
127
+ self,
128
+ rates,
129
+ modo="simples",
130
+ ancorar_abertura=False,
131
+ ) -> List[RenkoBrick]:
132
+
133
+ if not rates or len(rates) < 2:
134
+ return []
135
+
136
+ if modo == "classico":
137
+ return self._construir_classico(rates, ancorar_abertura)
138
+
139
+ return self._construir_simples(rates, ancorar_abertura)
140
+
141
+ # ======================================================
142
+ # RENKO SIMPLES
143
+ # ======================================================
144
+
145
+ def _construir_simples(self, rates, ancorar_abertura):
146
+
147
+ bricks: List[RenkoBrick] = []
148
+
149
+ last_price = float(rates[0]["open"]) if ancorar_abertura else float(
150
+ rates[0]["close"]
151
+ )
152
+
153
+ for rate in rates[1:]:
154
+
155
+ high = float(rate["high"])
156
+ low = float(rate["low"])
157
+
158
+ # Up bricks
159
+ while high - last_price >= self.brick_size:
160
+ novo = last_price + self.brick_size
161
+ bricks.append(RenkoBrick("up", last_price, novo))
162
+ last_price = novo
163
+
164
+ # Down bricks
165
+ while last_price - low >= self.brick_size:
166
+ novo = last_price - self.brick_size
167
+ bricks.append(RenkoBrick("down", last_price, novo))
168
+ last_price = novo
169
+
170
+ return bricks
171
+
172
+ # ======================================================
173
+ # RENKO CLÁSSICO (REVERSÃO 2x)
174
+ # ======================================================
175
+
176
+ def _construir_classico(self, rates, ancorar_abertura):
177
+
178
+ bricks: List[RenkoBrick] = []
179
+
180
+ last_price = float(rates[0]["open"]) if ancorar_abertura else float(
181
+ rates[0]["close"]
182
+ )
183
+
184
+ direction: Optional[str] = None
185
+
186
+ for rate in rates[1:]:
187
+
188
+ high = float(rate["high"])
189
+ low = float(rate["low"])
190
+
191
+ # -------------------------------------------------
192
+ # Continuação de alta
193
+ # -------------------------------------------------
194
+ if direction in (None, "up"):
195
+
196
+ while high - last_price >= self.brick_size:
197
+ novo = last_price + self.brick_size
198
+ bricks.append(RenkoBrick("up", last_price, novo))
199
+ last_price = novo
200
+ direction = "up"
201
+
202
+ # reversão 2x
203
+ if direction == "up" and last_price - low >= 2 * self.brick_size:
204
+ novo = last_price - self.brick_size
205
+ bricks.append(RenkoBrick("down", last_price, novo))
206
+ last_price = novo
207
+ direction = "down"
208
+
209
+ # -------------------------------------------------
210
+ # Continuação de baixa
211
+ # -------------------------------------------------
212
+ if direction in (None, "down"):
213
+
214
+ while last_price - low >= self.brick_size:
215
+ novo = last_price - self.brick_size
216
+ bricks.append(RenkoBrick("down", last_price, novo))
217
+ last_price = novo
218
+ direction = "down"
219
+
220
+ # reversão 2x
221
+ if direction == "down" and high - last_price >= 2 * self.brick_size:
222
+ novo = last_price + self.brick_size
223
+ bricks.append(RenkoBrick("up", last_price, novo))
224
+ last_price = novo
225
+ direction = "up"
226
+
227
+ return bricks
@@ -0,0 +1,5 @@
1
+ from .commands.renko import renko
2
+
3
+
4
+ def register(cli):
5
+ cli.add_command(renko, name="renko")
File without changes
@@ -0,0 +1,37 @@
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)
@@ -0,0 +1,80 @@
1
+ [project]
2
+ name = "mtcli-renko"
3
+ version = "1.0.0"
4
+ description = "Renko plugin institucional para mtcli (MetaTrader 5)"
5
+ authors = [
6
+ { name = "Valmir França", email = "vfranca3@gmail.com" }
7
+ ]
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.10,<3.14"
12
+
13
+ keywords = [
14
+ "trading",
15
+ "renko",
16
+ "metatrader5",
17
+ "mt5",
18
+ "cli",
19
+ "price-action"
20
+ ]
21
+
22
+ classifiers = [
23
+ "Development Status :: 5 - Production/Stable",
24
+ "Intended Audience :: Financial and Insurance Industry",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Operating System :: OS Independent",
31
+ "Topic :: Office/Business :: Financial :: Investment",
32
+ ]
33
+
34
+ dependencies = [
35
+ "mtcli>=3.2.0",
36
+ "click>=8.3.0,<9.0.0",
37
+ "metatrader5>=5.0.5370,<6.0.0"
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/vfranca/mtcli-renko"
42
+ Repository = "https://github.com/vfranca/mtcli-renko"
43
+ Issues = "https://github.com/vfranca/mtcli-renko/issues"
44
+ Documentation = "https://vfranca.github.io/mtcli-renko"
45
+
46
+ [project.entry-points."mtcli.plugins"]
47
+ renko = "mtcli_renko.plugin:register"
48
+
49
+ [build-system]
50
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
51
+ build-backend = "poetry.core.masonry.api"
52
+
53
+ [tool.poetry.group.dev.dependencies]
54
+ pytest = "^8.4.2"
55
+ pytest-cov = "^6.2.1"
56
+ pytest-mock = "^3.15.1"
57
+ ruff = "^0.14.1"
58
+
59
+ [tool.poetry.group.docs.dependencies]
60
+ mkdocs = "^1.6.1"
61
+ pymdown-extensions = "^10.16.1"
62
+
63
+ [tool.ruff]
64
+ line-length = 88
65
+ target-version = "py311"
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "B", "I", "UP", "N"]
69
+ ignore = ["E501"]
70
+
71
+ [tool.ruff.lint.isort]
72
+ known-first-party = ["mtcli", "mtcli_renko"]
73
+ combine-as-imports = true
74
+ force-sort-within-sections = true
75
+
76
+ [tool.ruff.format]
77
+ quote-style = "double"
78
+ indent-style = "space"
79
+ line-ending = "auto"
80
+ skip-magic-trailing-comma = false