mtcli-renko 1.1.1__tar.gz → 1.2.0.dev0__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.1 → mtcli_renko-1.2.0.dev0}/PKG-INFO +1 -1
  2. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/controllers/renko_controller.py +156 -156
  3. mtcli_renko-1.2.0.dev0/mtcli_renko/database.py +54 -0
  4. mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata/tick_cache.py +24 -0
  5. mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata/tick_repository.py +142 -0
  6. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/models/renko_model.py +29 -3
  7. mtcli_renko-1.2.0.dev0/mtcli_renko/views/__init__.py +0 -0
  8. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/views/renko_view.py +205 -200
  9. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/pyproject.toml +1 -1
  10. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/LICENSE +0 -0
  11. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/README.md +0 -0
  12. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/__init__.py +0 -0
  13. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/commands/__init__.py +0 -0
  14. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/commands/renko.py +0 -0
  15. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/conf.py +0 -0
  16. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/controllers/__init__.py +0 -0
  17. {mtcli_renko-1.1.1/mtcli_renko/models → mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata}/__init__.py +0 -0
  18. {mtcli_renko-1.1.1/mtcli_renko/utils → mtcli_renko-1.2.0.dev0/mtcli_renko/models}/__init__.py +0 -0
  19. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/plugin.py +0 -0
  20. {mtcli_renko-1.1.1/mtcli_renko/views → mtcli_renko-1.2.0.dev0/mtcli_renko/utils}/__init__.py +0 -0
  21. {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/utils/renko_stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli-renko
3
- Version: 1.1.1
3
+ Version: 1.2.0.dev0
4
4
  Summary: Renko plugin institucional para mtcli (MetaTrader 5)
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,156 +1,156 @@
1
- """
2
- Renko controller.
3
-
4
- Responsável por:
5
-
6
- - Orquestrar obtenção de dados (candle ou tick)
7
- - Chamar o model
8
- - Aplicar filtros e estilos
9
- """
10
-
11
- from ..models.renko_model import RenkoModel
12
- from mtcli.logger import setup_logger
13
-
14
- log = setup_logger(__name__)
15
-
16
-
17
- class RenkoController:
18
- """
19
- Controller principal do Renko.
20
-
21
- :param symbol: ativo
22
- :param brick_size: tamanho do brick
23
- :param timeframe: timeframe MT5
24
- :param quantidade: número de candles
25
- :param modo: simples | classico
26
- :param ancorar_abertura: ancora sessão
27
- :param data_mode: candle | tick
28
- :param max_ticks: limite ticks
29
- :param tick_style: estrutural | hibrido | agressivo
30
- :param price_min: filtro preço mínimo
31
- :param price_max: filtro preço máximo
32
- :param limit_bricks: limite de blocos
33
- :param reverse: inverter ordem
34
- """
35
-
36
- def __init__(
37
- self,
38
- symbol,
39
- brick_size,
40
- timeframe,
41
- quantidade,
42
- modo="simples",
43
- ancorar_abertura=False,
44
- data_mode="candle",
45
- max_ticks=3000,
46
- tick_style="hibrido",
47
- price_min=None,
48
- price_max=None,
49
- limit_bricks=None,
50
- reverse=False,
51
- ):
52
-
53
- self.model = RenkoModel(symbol, brick_size)
54
-
55
- self.timeframe = timeframe
56
- self.quantidade = quantidade
57
- self.modo = modo
58
-
59
- self.ancorar_abertura = ancorar_abertura
60
- self.data_mode = data_mode
61
- self.max_ticks = max_ticks
62
- self.tick_style = tick_style
63
-
64
- self.price_min = price_min
65
- self.price_max = price_max
66
- self.limit_bricks = limit_bricks
67
- self.reverse = reverse
68
-
69
- # ==========================================================
70
- # EXECUÇÃO
71
- # ==========================================================
72
-
73
- def executar(self):
74
-
75
- # ======================================================
76
- # TICK MODE
77
- # ======================================================
78
-
79
- if self.data_mode == "tick":
80
-
81
- ticks = self.model.obter_ticks(
82
- max_ticks=self.max_ticks,
83
- ancorar_abertura=self.ancorar_abertura,
84
- )
85
-
86
- if not ticks:
87
- log.warning("Nenhum tick retornado.")
88
- return []
89
-
90
- resultado = self.model.construir_renko_ticks(ticks, modo=self.modo)
91
-
92
- bricks = list(resultado.confirmados)
93
-
94
- # ======================================================
95
- # CANDLE MODE
96
- # ======================================================
97
-
98
- else:
99
-
100
- rates = self.model.obter_rates(
101
- self.timeframe,
102
- self.quantidade,
103
- ancorar_abertura=self.ancorar_abertura,
104
- )
105
-
106
- if not rates:
107
- log.warning("Nenhum candle retornado.")
108
- return []
109
-
110
- bricks = self.model.construir_renko(
111
- rates,
112
- modo=self.modo,
113
- )
114
-
115
- resultado = bricks
116
-
117
- # ======================================================
118
- # FILTROS
119
- # ======================================================
120
-
121
- if self.price_min is not None:
122
- bricks = [b for b in bricks if b.close >= self.price_min]
123
-
124
- if self.price_max is not None:
125
- bricks = [b for b in bricks if b.close <= self.price_max]
126
-
127
- if self.reverse:
128
- bricks = list(reversed(bricks))
129
-
130
- if self.limit_bricks:
131
- bricks = bricks[-self.limit_bricks:]
132
-
133
- # ======================================================
134
- # TICK STYLE
135
- # ======================================================
136
-
137
- if self.data_mode == "tick":
138
-
139
- # estrutural → apenas confirmados
140
- if self.tick_style == "estrutural":
141
- return bricks
142
-
143
- # agressivo → confirmados + formação como confirmado
144
- if self.tick_style == "agressivo":
145
-
146
- if resultado.em_formacao:
147
- bricks.append(resultado.em_formacao)
148
-
149
- return bricks
150
-
151
- # híbrido → confirmados + bloco separado
152
- resultado = resultado._replace(confirmados=bricks)
153
-
154
- return resultado
155
-
156
- return bricks
1
+ """
2
+ Renko controller.
3
+
4
+ Responsável por:
5
+
6
+ - Orquestrar obtenção de dados (candle ou tick)
7
+ - Chamar o model
8
+ - Aplicar filtros e estilos
9
+ """
10
+
11
+ from ..models.renko_model import RenkoModel
12
+ from mtcli.logger import setup_logger
13
+
14
+ log = setup_logger(__name__)
15
+
16
+
17
+ class RenkoController:
18
+ """
19
+ Controller principal do Renko.
20
+
21
+ :param symbol: ativo
22
+ :param brick_size: tamanho do brick
23
+ :param timeframe: timeframe MT5
24
+ :param quantidade: número de candles
25
+ :param modo: simples | classico
26
+ :param ancorar_abertura: ancora sessão
27
+ :param data_mode: candle | tick
28
+ :param max_ticks: limite ticks
29
+ :param tick_style: estrutural | hibrido | agressivo
30
+ :param price_min: filtro preço mínimo
31
+ :param price_max: filtro preço máximo
32
+ :param limit_bricks: limite de blocos
33
+ :param reverse: inverter ordem
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ symbol,
39
+ brick_size,
40
+ timeframe,
41
+ quantidade,
42
+ modo="simples",
43
+ ancorar_abertura=False,
44
+ data_mode="candle",
45
+ max_ticks=3000,
46
+ tick_style="hibrido",
47
+ price_min=None,
48
+ price_max=None,
49
+ limit_bricks=None,
50
+ reverse=False,
51
+ ):
52
+
53
+ self.model = RenkoModel(symbol, brick_size)
54
+
55
+ self.timeframe = timeframe
56
+ self.quantidade = quantidade
57
+ self.modo = modo
58
+
59
+ self.ancorar_abertura = ancorar_abertura
60
+ self.data_mode = data_mode
61
+ self.max_ticks = max_ticks
62
+ self.tick_style = tick_style
63
+
64
+ self.price_min = price_min
65
+ self.price_max = price_max
66
+ self.limit_bricks = limit_bricks
67
+ self.reverse = reverse
68
+
69
+ # ==========================================================
70
+ # EXECUÇÃO
71
+ # ==========================================================
72
+
73
+ def executar(self):
74
+
75
+ # ======================================================
76
+ # TICK MODE
77
+ # ======================================================
78
+
79
+ if self.data_mode == "tick":
80
+
81
+ ticks = self.model.obter_ticks(
82
+ max_ticks=self.max_ticks,
83
+ ancorar_abertura=self.ancorar_abertura,
84
+ )
85
+
86
+ if not ticks:
87
+ log.warning("Nenhum tick retornado.")
88
+ return []
89
+
90
+ resultado = self.model.construir_renko_ticks(ticks, modo=self.modo)
91
+
92
+ bricks = list(resultado.confirmados)
93
+
94
+ # ======================================================
95
+ # CANDLE MODE
96
+ # ======================================================
97
+
98
+ else:
99
+
100
+ rates = self.model.obter_rates(
101
+ self.timeframe,
102
+ self.quantidade,
103
+ ancorar_abertura=self.ancorar_abertura,
104
+ )
105
+
106
+ if not rates:
107
+ log.warning("Nenhum candle retornado.")
108
+ return []
109
+
110
+ bricks = self.model.construir_renko(
111
+ rates,
112
+ modo=self.modo,
113
+ )
114
+
115
+ resultado = bricks
116
+
117
+ # ======================================================
118
+ # FILTROS
119
+ # ======================================================
120
+
121
+ if self.price_min is not None:
122
+ bricks = [b for b in bricks if b.close >= self.price_min]
123
+
124
+ if self.price_max is not None:
125
+ bricks = [b for b in bricks if b.close <= self.price_max]
126
+
127
+ if self.reverse:
128
+ bricks = list(reversed(bricks))
129
+
130
+ if self.limit_bricks:
131
+ bricks = bricks[-self.limit_bricks:]
132
+
133
+ # ======================================================
134
+ # TICK STYLE
135
+ # ======================================================
136
+
137
+ if self.data_mode == "tick":
138
+
139
+ # estrutural → apenas confirmados
140
+ if self.tick_style == "estrutural":
141
+ return bricks
142
+
143
+ # agressivo → confirmados + formação como confirmado
144
+ if self.tick_style == "agressivo":
145
+
146
+ if resultado.em_formacao:
147
+ bricks.append(resultado.em_formacao)
148
+
149
+ return bricks
150
+
151
+ # híbrido → confirmados + bloco separado
152
+ resultado = resultado._replace(confirmados=bricks)
153
+
154
+ return resultado
155
+
156
+ return bricks
@@ -0,0 +1,54 @@
1
+ """
2
+ Database core para mtcli.
3
+
4
+ Responsável por:
5
+ - Criar conexão SQLite
6
+ - Ativar WAL
7
+ - Garantir schema
8
+ """
9
+
10
+ import sqlite3
11
+ from pathlib import Path
12
+
13
+
14
+ DB_PATH = Path.home() / ".mtcli" / "marketdata.db"
15
+
16
+
17
+ def get_connection():
18
+ """
19
+ Retorna conexão SQLite configurada.
20
+ """
21
+ DB_PATH.parent.mkdir(parents=True, exist_ok=True)
22
+
23
+ conn = sqlite3.connect(DB_PATH)
24
+ conn.execute("PRAGMA journal_mode=WAL;")
25
+ conn.execute("PRAGMA synchronous=NORMAL;")
26
+
27
+ _ensure_schema(conn)
28
+ return conn
29
+
30
+
31
+ def _ensure_schema(conn):
32
+ conn.execute(
33
+ """
34
+ CREATE TABLE IF NOT EXISTS ticks (
35
+ symbol TEXT NOT NULL,
36
+ time INTEGER NOT NULL,
37
+ bid REAL,
38
+ ask REAL,
39
+ last REAL,
40
+ volume REAL,
41
+ flags INTEGER,
42
+ PRIMARY KEY (symbol, time)
43
+ );
44
+ """
45
+ )
46
+
47
+ conn.execute(
48
+ """
49
+ CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
50
+ ON ticks(symbol, time);
51
+ """
52
+ )
53
+
54
+ conn.commit()
@@ -0,0 +1,24 @@
1
+ """
2
+ Cache de ticks em memória.
3
+ """
4
+
5
+ from collections import deque
6
+
7
+
8
+ class TickCache:
9
+ """
10
+ Mantém janela recente de ticks em memória.
11
+ """
12
+
13
+ def __init__(self, max_size=10000):
14
+ self.buffer = deque(maxlen=max_size)
15
+
16
+ def add_many(self, ticks):
17
+ for t in ticks:
18
+ self.buffer.append(t)
19
+
20
+ def get_all(self):
21
+ return list(self.buffer)
22
+
23
+ def clear(self):
24
+ self.buffer.clear()
@@ -0,0 +1,142 @@
1
+ """
2
+ TickRepository profissional com:
3
+
4
+ - Paginação automática
5
+ - Sincronização incremental robusta
6
+ - Integração com mt5_conexao
7
+ - Proteção contra loops infinitos
8
+ """
9
+
10
+ import MetaTrader5 as mt5
11
+ from datetime import datetime, timedelta
12
+
13
+ from ..database import get_connection
14
+ from .tick_cache import TickCache
15
+ from mtcli.mt5_context import mt5_conexao
16
+
17
+
18
+ class TickRepository:
19
+
20
+ BATCH_SIZE = 200000 # tamanho seguro por lote
21
+
22
+ def __init__(self):
23
+ self.conn = get_connection()
24
+ self.cache = TickCache()
25
+
26
+ # ============================================================
27
+ # SINCRONIZAÇÃO COM PAGINAÇÃO
28
+ # ============================================================
29
+
30
+ def sync(self, symbol: str, days_back: int = 1):
31
+ """
32
+ Sincroniza banco com MT5 usando paginação.
33
+
34
+ Busca todos os ticks disponíveis desde o último timestamp.
35
+ """
36
+
37
+ total_inserted = 0
38
+
39
+ with mt5_conexao():
40
+
41
+ last_time = self._get_last_tick_time(symbol)
42
+
43
+ if last_time:
44
+ start = datetime.fromtimestamp(last_time + 1)
45
+ else:
46
+ start = datetime.now() - timedelta(days=days_back)
47
+
48
+ while True:
49
+
50
+ ticks = mt5.copy_ticks_from(
51
+ symbol,
52
+ start,
53
+ self.BATCH_SIZE,
54
+ mt5.COPY_TICKS_ALL,
55
+ )
56
+
57
+ if ticks is None or len(ticks) == 0:
58
+ break
59
+
60
+ inserted = self._insert_ticks(symbol, ticks)
61
+ total_inserted += inserted
62
+
63
+ self.cache.add_many(ticks)
64
+
65
+ # Atualiza início para próximo tick
66
+ ultimo_timestamp = int(ticks[-1]["time"])
67
+ novo_start = datetime.fromtimestamp(ultimo_timestamp + 1)
68
+
69
+ # Proteção contra loop infinito
70
+ if novo_start <= start:
71
+ break
72
+
73
+ start = novo_start
74
+
75
+ # Se retornou menos que o batch, acabou histórico
76
+ if len(ticks) < self.BATCH_SIZE:
77
+ break
78
+
79
+ return total_inserted
80
+
81
+ # ============================================================
82
+ # INSERÇÃO
83
+ # ============================================================
84
+
85
+ def _insert_ticks(self, symbol, ticks):
86
+
87
+ cursor = self.conn.cursor()
88
+
89
+ data = [
90
+ (
91
+ symbol,
92
+ int(t["time"]),
93
+ float(t["bid"]),
94
+ float(t["ask"]),
95
+ float(t["last"]),
96
+ float(t["volume"]),
97
+ int(t["flags"]),
98
+ )
99
+ for t in ticks
100
+ ]
101
+
102
+ cursor.executemany(
103
+ """
104
+ INSERT OR IGNORE INTO ticks
105
+ VALUES (?, ?, ?, ?, ?, ?, ?)
106
+ """,
107
+ data,
108
+ )
109
+
110
+ self.conn.commit()
111
+ return cursor.rowcount
112
+
113
+ # ============================================================
114
+ # CONSULTAS
115
+ # ============================================================
116
+
117
+ def get_ticks_between(self, symbol, start_ts, end_ts):
118
+ cursor = self.conn.cursor()
119
+ cursor.execute(
120
+ """
121
+ SELECT time, bid, ask, last, volume, flags
122
+ FROM ticks
123
+ WHERE symbol = ?
124
+ AND time BETWEEN ? AND ?
125
+ ORDER BY time ASC
126
+ """,
127
+ (symbol, start_ts, end_ts),
128
+ )
129
+ return cursor.fetchall()
130
+
131
+ def _get_last_tick_time(self, symbol):
132
+ cursor = self.conn.cursor()
133
+ cursor.execute(
134
+ """
135
+ SELECT MAX(time)
136
+ FROM ticks
137
+ WHERE symbol = ?
138
+ """,
139
+ (symbol,),
140
+ )
141
+ result = cursor.fetchone()
142
+ return result[0] if result and result[0] else None
@@ -19,7 +19,7 @@ import MetaTrader5 as mt5
19
19
 
20
20
  from mtcli.mt5_context import mt5_conexao
21
21
  from mtcli.logger import setup_logger
22
- from mtcli.marketdata.tick_repository import TickRepository
22
+ from ..marketdata.tick_repository import TickRepository
23
23
  from ..conf import SESSION_OPEN, SESSION_OPEN_OFFSET_SECONDS, BROKER_UTC_OFFSET
24
24
 
25
25
  log = setup_logger(__name__)
@@ -34,6 +34,8 @@ class Brick:
34
34
  direction: str
35
35
  open: float
36
36
  close: float
37
+ volume: float = 0
38
+ ticks: int = 0
37
39
 
38
40
 
39
41
  class RenkoTickResult(NamedTuple):
@@ -253,10 +255,16 @@ class RenkoModel:
253
255
 
254
256
  last_price = float(ticks[0][3])
255
257
  last_direction: Optional[str] = None
258
+ volume_acumulado = 0
259
+ ticks_acumulados = 0
256
260
 
257
261
  for tick in ticks[1:]:
258
262
 
259
263
  price = float(tick[3])
264
+ volume = float(tick[4])
265
+
266
+ volume_acumulado += volume
267
+ ticks_acumulados += 1
260
268
 
261
269
  # =============================
262
270
  # movimento de alta
@@ -274,9 +282,17 @@ class RenkoModel:
274
282
  novo = last_price + self.brick_size
275
283
 
276
284
  bricks.append(
277
- Brick("up", last_price, novo)
285
+ Brick(
286
+ direction="up",
287
+ open=last_price,
288
+ close=novo,
289
+ volume=volume_acumulado,
290
+ ticks=ticks_acumulados,
291
+ )
278
292
  )
279
293
 
294
+ volume_acumulado = 0
295
+ ticks_acumulados = 0
280
296
  last_price = novo
281
297
  last_direction = "up"
282
298
 
@@ -296,9 +312,17 @@ class RenkoModel:
296
312
  novo = last_price - self.brick_size
297
313
 
298
314
  bricks.append(
299
- Brick("down", last_price, novo)
315
+ Brick(
316
+ direction="down",
317
+ open=last_price,
318
+ close=novo,
319
+ volume=volume_acumulado,
320
+ ticks=ticks_acumulados,
321
+ )
300
322
  )
301
323
 
324
+ volume_acumulado = 0
325
+ ticks_acumulados = 0
302
326
  last_price = novo
303
327
  last_direction = "down"
304
328
 
@@ -320,6 +344,8 @@ class RenkoModel:
320
344
  direction=direcao,
321
345
  open=last_price,
322
346
  close=ultimo_preco,
347
+ volume=volume_acumulado,
348
+ ticks=ticks_acumulados,
323
349
  )
324
350
 
325
351
  return RenkoTickResult(
File without changes
@@ -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 Volume Abertura Fechamento")
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.volume:.0f} "
172
+ f"{brick.open:.{DIGITS}f} "
173
+ f"{brick.close:.{DIGITS}f}"
174
+ )
175
+ else:
176
+ linha = (
177
+ f"{simbolo} "
178
+ f"{brick.volume:.0f} "
179
+ f"{brick.open:.{DIGITS}f} "
180
+ f"{brick.close:.{DIGITS}f}"
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.volume:.0f} "
201
+ f"{em_formacao.open:.{DIGITS}f} "
202
+ f"{em_formacao.close:.{DIGITS}f}"
203
+ )
204
+
205
+ click.echo(linha)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli-renko"
3
- version = "1.1.1"
3
+ version = "1.2.0.dev0"
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