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.
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/PKG-INFO +1 -1
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/controllers/renko_controller.py +156 -156
- mtcli_renko-1.2.0.dev0/mtcli_renko/database.py +54 -0
- mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata/tick_cache.py +24 -0
- mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata/tick_repository.py +142 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/models/renko_model.py +29 -3
- mtcli_renko-1.2.0.dev0/mtcli_renko/views/__init__.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/views/renko_view.py +205 -200
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/pyproject.toml +1 -1
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/LICENSE +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/README.md +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/__init__.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/commands/__init__.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/commands/renko.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/conf.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/controllers/__init__.py +0 -0
- {mtcli_renko-1.1.1/mtcli_renko/models → mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata}/__init__.py +0 -0
- {mtcli_renko-1.1.1/mtcli_renko/utils → mtcli_renko-1.2.0.dev0/mtcli_renko/models}/__init__.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/plugin.py +0 -0
- {mtcli_renko-1.1.1/mtcli_renko/views → mtcli_renko-1.2.0.dev0/mtcli_renko/utils}/__init__.py +0 -0
- {mtcli_renko-1.1.1 → mtcli_renko-1.2.0.dev0}/mtcli_renko/utils/renko_stats.py +0 -0
|
@@ -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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
simbolo =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
f"{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mtcli_renko-1.1.1/mtcli_renko/models → mtcli_renko-1.2.0.dev0/mtcli_renko/marketdata}/__init__.py
RENAMED
|
File without changes
|
{mtcli_renko-1.1.1/mtcli_renko/utils → mtcli_renko-1.2.0.dev0/mtcli_renko/models}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{mtcli_renko-1.1.1/mtcli_renko/views → mtcli_renko-1.2.0.dev0/mtcli_renko/utils}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|