mtcli 3.6.0.dev0__tar.gz → 3.6.1__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-3.6.0.dev0 → mtcli-3.6.1}/PKG-INFO +1 -1
- mtcli-3.6.1/mtcli/cli.py +34 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/conf.py +199 -199
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/csv.py +31 -31
- mtcli-3.6.1/mtcli/logger.py +136 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/consecutive_bars_model.py +77 -77
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/unconsecutive_bar_model.py +67 -67
- mtcli-3.6.1/mtcli/plugin_loader.py +97 -0
- mtcli-3.6.1/mtcli/plugins/exemplo.py-dist +30 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/cli.py +98 -98
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/test_mm.py +13 -13
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/cli.py +32 -32
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/models/average_range_model.py +29 -29
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/tests/test_rm.py +11 -11
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/cli.py +41 -41
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/models/model_average_volume.py +31 -31
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/tests/test_vm.py +21 -21
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/close_view.py +37 -37
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/full_view.py +65 -65
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/high_view.py +37 -37
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/low_view.py +37 -37
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/min_view.py +42 -42
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/open_view.py +37 -37
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/ranges_view.py +41 -41
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/rates_view.py +41 -41
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/pyproject.toml +1 -1
- mtcli-3.6.0.dev0/mtcli/cli.py +0 -32
- mtcli-3.6.0.dev0/mtcli/logger.py +0 -47
- mtcli-3.6.0.dev0/mtcli/plugin_loader.py +0 -57
- mtcli-3.6.0.dev0/mtcli/plugins/hello.py +0 -20
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/LICENSE +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/README.md +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/commands/bars.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/conecta.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/base.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/mt5.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/database.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/tick_repository.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/mt5_context.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugin.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/__init__.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/volumes_view.py +0 -0
mtcli-3.6.1/mtcli/cli.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI principal do mtcli.
|
|
3
|
+
|
|
4
|
+
Este módulo define o grupo principal `mt`
|
|
5
|
+
e inicializa o carregamento de plugins.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from mtcli.plugin_loader import load_plugins
|
|
11
|
+
from .commands.bars import bars
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group(context_settings={"max_content_width": 120})
|
|
15
|
+
@click.version_option(package_name="mtcli")
|
|
16
|
+
def mt():
|
|
17
|
+
"""
|
|
18
|
+
CLI principal do mtcli.
|
|
19
|
+
|
|
20
|
+
Exibe gráficos e informações de mercado
|
|
21
|
+
em formato textual compatível com leitores de tela.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
mt.add_command(bars, name="bars")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Carrega plugins automaticamente
|
|
30
|
+
load_plugins(mt)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
mt()
|
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Configurações principais do mtcli.
|
|
3
|
-
|
|
4
|
-
Este módulo centraliza a leitura de configurações provenientes de:
|
|
5
|
-
|
|
6
|
-
1. Variáveis de ambiente
|
|
7
|
-
2. Arquivo mtcli.ini
|
|
8
|
-
|
|
9
|
-
As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
|
|
10
|
-
|
|
11
|
-
Também fornece utilidades para obter o caminho de arquivos do
|
|
12
|
-
terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
|
|
13
|
-
|
|
14
|
-
O acesso ao terminal MT5 é realizado apenas sob demanda para evitar
|
|
15
|
-
efeitos colaterais durante a importação do módulo (import side effects),
|
|
16
|
-
facilitando testes automatizados e execução em ambientes CI/CD.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import os
|
|
20
|
-
import configparser
|
|
21
|
-
|
|
22
|
-
import MetaTrader5 as mt5
|
|
23
|
-
|
|
24
|
-
from mtcli.mt5_context import mt5_conexao
|
|
25
|
-
|
|
26
|
-
# ---------------------------------------------------------
|
|
27
|
-
# Carregamento do arquivo de configuração
|
|
28
|
-
# ---------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
CONFIG_FILE = "mtcli.ini"
|
|
31
|
-
SECTION = "DEFAULT"
|
|
32
|
-
|
|
33
|
-
config = configparser.ConfigParser()
|
|
34
|
-
config.read(CONFIG_FILE)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def get_config_value(key: str, cast=None, fallback=None):
|
|
38
|
-
"""
|
|
39
|
-
Retorna um valor de configuração.
|
|
40
|
-
|
|
41
|
-
A prioridade de leitura é:
|
|
42
|
-
1. Variável de ambiente
|
|
43
|
-
2. Arquivo mtcli.ini
|
|
44
|
-
3. Valor fallback
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
key (str): Nome da chave de configuração.
|
|
48
|
-
cast (type | None): Tipo de conversão (ex: int, float).
|
|
49
|
-
fallback (Any): Valor padrão caso não encontrado.
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Any: valor convertido ou fallback.
|
|
53
|
-
"""
|
|
54
|
-
value = os.getenv(key)
|
|
55
|
-
|
|
56
|
-
if value is None:
|
|
57
|
-
try:
|
|
58
|
-
if cast == int:
|
|
59
|
-
value = config.getint(SECTION, key, fallback=fallback)
|
|
60
|
-
elif cast == float:
|
|
61
|
-
value = config.getfloat(SECTION, key, fallback=fallback)
|
|
62
|
-
else:
|
|
63
|
-
value = config.get(SECTION, key, fallback=fallback)
|
|
64
|
-
except (configparser.NoOptionError, ValueError):
|
|
65
|
-
value = fallback
|
|
66
|
-
else:
|
|
67
|
-
if cast:
|
|
68
|
-
try:
|
|
69
|
-
value = cast(value)
|
|
70
|
-
except ValueError:
|
|
71
|
-
value = fallback
|
|
72
|
-
|
|
73
|
-
return value
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# ---------------------------------------------------------
|
|
77
|
-
# Configurações gerais
|
|
78
|
-
# ---------------------------------------------------------
|
|
79
|
-
|
|
80
|
-
SYMBOL = get_config_value("symbol", fallback="WIN$N")
|
|
81
|
-
DIGITOS = get_config_value("digitos", cast=int, fallback=2)
|
|
82
|
-
PERIOD = get_config_value("period", fallback="D1")
|
|
83
|
-
BARS = get_config_value("count", cast=int, fallback=999)
|
|
84
|
-
|
|
85
|
-
VIEW = get_config_value("view", fallback="ch")
|
|
86
|
-
VOLUME = get_config_value("volume", fallback="tick")
|
|
87
|
-
DATE = get_config_value("date", fallback="")
|
|
88
|
-
|
|
89
|
-
# ---------------------------------------------------------
|
|
90
|
-
# Configurações de leitura de candles
|
|
91
|
-
# ---------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
LATERAL = get_config_value("lateral", fallback="doji")
|
|
94
|
-
ALTA = get_config_value("alta", fallback="verde")
|
|
95
|
-
BAIXA = get_config_value("baixa", fallback="vermelho")
|
|
96
|
-
|
|
97
|
-
ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
|
|
98
|
-
ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
|
|
99
|
-
|
|
100
|
-
PERCENTUAL_ROMPIMENTO = get_config_value(
|
|
101
|
-
"percentual_rompimento", cast=int, fallback=50
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
PERCENTUAL_DOJI = get_config_value(
|
|
105
|
-
"percentual_doji", cast=int, fallback=10
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# ---------------------------------------------------------
|
|
109
|
-
# Configurações de padrões de barra
|
|
110
|
-
# ---------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
UP_BAR = get_config_value("up_bar", fallback="asc")
|
|
113
|
-
DOWN_BAR = get_config_value("down_bar", fallback="desc")
|
|
114
|
-
|
|
115
|
-
INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
|
|
116
|
-
OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
|
|
117
|
-
|
|
118
|
-
SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
|
|
119
|
-
SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
|
|
120
|
-
|
|
121
|
-
# ---------------------------------------------------------
|
|
122
|
-
# Fonte de dados
|
|
123
|
-
# ---------------------------------------------------------
|
|
124
|
-
|
|
125
|
-
DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
|
|
126
|
-
|
|
127
|
-
# caminho inicial (pode vir do ini/env)
|
|
128
|
-
_INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def get_csv_path():
|
|
132
|
-
"""
|
|
133
|
-
Retorna o caminho da pasta de arquivos do MT5 ou CSV.
|
|
134
|
-
|
|
135
|
-
Se o caminho não estiver definido no mtcli.ini ou nas
|
|
136
|
-
variáveis de ambiente, o terminal MT5 é consultado para
|
|
137
|
-
descobrir automaticamente a pasta MQL5/Files.
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
str: caminho normalizado da pasta de arquivos.
|
|
141
|
-
"""
|
|
142
|
-
if _INITIAL_CSV_PATH:
|
|
143
|
-
path = _INITIAL_CSV_PATH
|
|
144
|
-
else:
|
|
145
|
-
with mt5_conexao():
|
|
146
|
-
terminal_info = mt5.terminal_info()
|
|
147
|
-
|
|
148
|
-
if terminal_info is None:
|
|
149
|
-
raise RuntimeError(
|
|
150
|
-
"Não foi possível obter as informações do terminal MT5."
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
path = os.path.join(terminal_info.data_path, "MQL5", "Files")
|
|
154
|
-
|
|
155
|
-
return os.path.normpath(path) + os.sep
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# ---------------------------------------------------------
|
|
159
|
-
# Factory de DataSource
|
|
160
|
-
# ---------------------------------------------------------
|
|
161
|
-
|
|
162
|
-
def get_data_source(source=None):
|
|
163
|
-
"""
|
|
164
|
-
Retorna a fonte de dados configurada.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
source (str | None): sobrescreve DATA_SOURCE se fornecido.
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
CsvDataSource | MT5DataSource
|
|
171
|
-
|
|
172
|
-
Raises:
|
|
173
|
-
ValueError: se a fonte de dados não for reconhecida.
|
|
174
|
-
"""
|
|
175
|
-
from mtcli.data import CsvDataSource, MT5DataSource
|
|
176
|
-
|
|
177
|
-
src = source.lower() if source else DATA_SOURCE
|
|
178
|
-
|
|
179
|
-
if src == "csv":
|
|
180
|
-
return CsvDataSource()
|
|
181
|
-
|
|
182
|
-
if src == "mt5":
|
|
183
|
-
return MT5DataSource()
|
|
184
|
-
|
|
185
|
-
raise ValueError(f"Fonte de dados desconhecida: {src}")
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
# ---------------------------------------------------------
|
|
189
|
-
# Timeframes suportados
|
|
190
|
-
# ---------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
_HOURS = [12, 8, 6, 4, 3, 2, 1]
|
|
193
|
-
_MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
|
|
194
|
-
|
|
195
|
-
TIMEFRAMES = (
|
|
196
|
-
["mn1", "w1", "d1"]
|
|
197
|
-
+ [f"h{i}" for i in _HOURS]
|
|
198
|
-
+ [f"m{i}" for i in _MINUTES]
|
|
199
|
-
)
|
|
1
|
+
"""
|
|
2
|
+
Configurações principais do mtcli.
|
|
3
|
+
|
|
4
|
+
Este módulo centraliza a leitura de configurações provenientes de:
|
|
5
|
+
|
|
6
|
+
1. Variáveis de ambiente
|
|
7
|
+
2. Arquivo mtcli.ini
|
|
8
|
+
|
|
9
|
+
As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
|
|
10
|
+
|
|
11
|
+
Também fornece utilidades para obter o caminho de arquivos do
|
|
12
|
+
terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
|
|
13
|
+
|
|
14
|
+
O acesso ao terminal MT5 é realizado apenas sob demanda para evitar
|
|
15
|
+
efeitos colaterais durante a importação do módulo (import side effects),
|
|
16
|
+
facilitando testes automatizados e execução em ambientes CI/CD.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import configparser
|
|
21
|
+
|
|
22
|
+
import MetaTrader5 as mt5
|
|
23
|
+
|
|
24
|
+
from mtcli.mt5_context import mt5_conexao
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------
|
|
27
|
+
# Carregamento do arquivo de configuração
|
|
28
|
+
# ---------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
CONFIG_FILE = "mtcli.ini"
|
|
31
|
+
SECTION = "DEFAULT"
|
|
32
|
+
|
|
33
|
+
config = configparser.ConfigParser()
|
|
34
|
+
config.read(CONFIG_FILE)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_config_value(key: str, cast=None, fallback=None):
|
|
38
|
+
"""
|
|
39
|
+
Retorna um valor de configuração.
|
|
40
|
+
|
|
41
|
+
A prioridade de leitura é:
|
|
42
|
+
1. Variável de ambiente
|
|
43
|
+
2. Arquivo mtcli.ini
|
|
44
|
+
3. Valor fallback
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
key (str): Nome da chave de configuração.
|
|
48
|
+
cast (type | None): Tipo de conversão (ex: int, float).
|
|
49
|
+
fallback (Any): Valor padrão caso não encontrado.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Any: valor convertido ou fallback.
|
|
53
|
+
"""
|
|
54
|
+
value = os.getenv(key)
|
|
55
|
+
|
|
56
|
+
if value is None:
|
|
57
|
+
try:
|
|
58
|
+
if cast == int:
|
|
59
|
+
value = config.getint(SECTION, key, fallback=fallback)
|
|
60
|
+
elif cast == float:
|
|
61
|
+
value = config.getfloat(SECTION, key, fallback=fallback)
|
|
62
|
+
else:
|
|
63
|
+
value = config.get(SECTION, key, fallback=fallback)
|
|
64
|
+
except (configparser.NoOptionError, ValueError):
|
|
65
|
+
value = fallback
|
|
66
|
+
else:
|
|
67
|
+
if cast:
|
|
68
|
+
try:
|
|
69
|
+
value = cast(value)
|
|
70
|
+
except ValueError:
|
|
71
|
+
value = fallback
|
|
72
|
+
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------
|
|
77
|
+
# Configurações gerais
|
|
78
|
+
# ---------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
SYMBOL = get_config_value("symbol", fallback="WIN$N")
|
|
81
|
+
DIGITOS = get_config_value("digitos", cast=int, fallback=2)
|
|
82
|
+
PERIOD = get_config_value("period", fallback="D1")
|
|
83
|
+
BARS = get_config_value("count", cast=int, fallback=999)
|
|
84
|
+
|
|
85
|
+
VIEW = get_config_value("view", fallback="ch")
|
|
86
|
+
VOLUME = get_config_value("volume", fallback="tick")
|
|
87
|
+
DATE = get_config_value("date", fallback="")
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------
|
|
90
|
+
# Configurações de leitura de candles
|
|
91
|
+
# ---------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
LATERAL = get_config_value("lateral", fallback="doji")
|
|
94
|
+
ALTA = get_config_value("alta", fallback="verde")
|
|
95
|
+
BAIXA = get_config_value("baixa", fallback="vermelho")
|
|
96
|
+
|
|
97
|
+
ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
|
|
98
|
+
ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
|
|
99
|
+
|
|
100
|
+
PERCENTUAL_ROMPIMENTO = get_config_value(
|
|
101
|
+
"percentual_rompimento", cast=int, fallback=50
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
PERCENTUAL_DOJI = get_config_value(
|
|
105
|
+
"percentual_doji", cast=int, fallback=10
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------
|
|
109
|
+
# Configurações de padrões de barra
|
|
110
|
+
# ---------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
UP_BAR = get_config_value("up_bar", fallback="asc")
|
|
113
|
+
DOWN_BAR = get_config_value("down_bar", fallback="desc")
|
|
114
|
+
|
|
115
|
+
INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
|
|
116
|
+
OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
|
|
117
|
+
|
|
118
|
+
SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
|
|
119
|
+
SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------
|
|
122
|
+
# Fonte de dados
|
|
123
|
+
# ---------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
|
|
126
|
+
|
|
127
|
+
# caminho inicial (pode vir do ini/env)
|
|
128
|
+
_INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_csv_path():
|
|
132
|
+
"""
|
|
133
|
+
Retorna o caminho da pasta de arquivos do MT5 ou CSV.
|
|
134
|
+
|
|
135
|
+
Se o caminho não estiver definido no mtcli.ini ou nas
|
|
136
|
+
variáveis de ambiente, o terminal MT5 é consultado para
|
|
137
|
+
descobrir automaticamente a pasta MQL5/Files.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
str: caminho normalizado da pasta de arquivos.
|
|
141
|
+
"""
|
|
142
|
+
if _INITIAL_CSV_PATH:
|
|
143
|
+
path = _INITIAL_CSV_PATH
|
|
144
|
+
else:
|
|
145
|
+
with mt5_conexao():
|
|
146
|
+
terminal_info = mt5.terminal_info()
|
|
147
|
+
|
|
148
|
+
if terminal_info is None:
|
|
149
|
+
raise RuntimeError(
|
|
150
|
+
"Não foi possível obter as informações do terminal MT5."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
path = os.path.join(terminal_info.data_path, "MQL5", "Files")
|
|
154
|
+
|
|
155
|
+
return os.path.normpath(path) + os.sep
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------
|
|
159
|
+
# Factory de DataSource
|
|
160
|
+
# ---------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def get_data_source(source=None):
|
|
163
|
+
"""
|
|
164
|
+
Retorna a fonte de dados configurada.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
source (str | None): sobrescreve DATA_SOURCE se fornecido.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
CsvDataSource | MT5DataSource
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: se a fonte de dados não for reconhecida.
|
|
174
|
+
"""
|
|
175
|
+
from mtcli.data import CsvDataSource, MT5DataSource
|
|
176
|
+
|
|
177
|
+
src = source.lower() if source else DATA_SOURCE
|
|
178
|
+
|
|
179
|
+
if src == "csv":
|
|
180
|
+
return CsvDataSource()
|
|
181
|
+
|
|
182
|
+
if src == "mt5":
|
|
183
|
+
return MT5DataSource()
|
|
184
|
+
|
|
185
|
+
raise ValueError(f"Fonte de dados desconhecida: {src}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------
|
|
189
|
+
# Timeframes suportados
|
|
190
|
+
# ---------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
_HOURS = [12, 8, 6, 4, 3, 2, 1]
|
|
193
|
+
_MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
|
|
194
|
+
|
|
195
|
+
TIMEFRAMES = (
|
|
196
|
+
["mn1", "w1", "d1"]
|
|
197
|
+
+ [f"h{i}" for i in _HOURS]
|
|
198
|
+
+ [f"m{i}" for i in _MINUTES]
|
|
199
|
+
)
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
"""Módulo fonte de dados via CSV."""
|
|
2
|
-
|
|
3
|
-
import csv
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
from mtcli import conf
|
|
7
|
-
from mtcli.logger import setup_logger
|
|
8
|
-
|
|
9
|
-
from .base import DataSourceBase
|
|
10
|
-
|
|
11
|
-
logger = setup_logger()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class CsvDataSource(DataSourceBase):
|
|
15
|
-
"""Fonte de dados via CSV."""
|
|
16
|
-
|
|
17
|
-
def get_data(self, symbol, period, count=100):
|
|
18
|
-
"""Retorna dados CSV em uma lista de lista."""
|
|
19
|
-
file_path = os.path.join(conf._INITIAL_CSV_PATH, f"{symbol}{period}.csv")
|
|
20
|
-
logger.info(f"Iniciando coleta de dados via CSV: {file_path}.")
|
|
21
|
-
csv_data = []
|
|
22
|
-
try:
|
|
23
|
-
with open(file_path, encoding="utf-16", newline="") as f:
|
|
24
|
-
lines = csv.reader(f, delimiter=",", quotechar="'")
|
|
25
|
-
for line in lines:
|
|
26
|
-
csv_data.append(line)
|
|
27
|
-
except:
|
|
28
|
-
logger.warning(f"Arquivo {file_path} não encontrado.")
|
|
29
|
-
print("Arquivo %s nao encontrado! Tente novamente" % file_path)
|
|
30
|
-
logger.info("Coleta de dados via CSV finalizada.")
|
|
31
|
-
return csv_data
|
|
1
|
+
"""Módulo fonte de dados via CSV."""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from mtcli import conf
|
|
7
|
+
from mtcli.logger import setup_logger
|
|
8
|
+
|
|
9
|
+
from .base import DataSourceBase
|
|
10
|
+
|
|
11
|
+
logger = setup_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CsvDataSource(DataSourceBase):
|
|
15
|
+
"""Fonte de dados via CSV."""
|
|
16
|
+
|
|
17
|
+
def get_data(self, symbol, period, count=100):
|
|
18
|
+
"""Retorna dados CSV em uma lista de lista."""
|
|
19
|
+
file_path = os.path.join(conf._INITIAL_CSV_PATH, f"{symbol}{period}.csv")
|
|
20
|
+
logger.info(f"Iniciando coleta de dados via CSV: {file_path}.")
|
|
21
|
+
csv_data = []
|
|
22
|
+
try:
|
|
23
|
+
with open(file_path, encoding="utf-16", newline="") as f:
|
|
24
|
+
lines = csv.reader(f, delimiter=",", quotechar="'")
|
|
25
|
+
for line in lines:
|
|
26
|
+
csv_data.append(line)
|
|
27
|
+
except:
|
|
28
|
+
logger.warning(f"Arquivo {file_path} não encontrado.")
|
|
29
|
+
print("Arquivo %s nao encontrado! Tente novamente" % file_path)
|
|
30
|
+
logger.info("Coleta de dados via CSV finalizada.")
|
|
31
|
+
return csv_data
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sistema central de logging do mtcli.
|
|
3
|
+
|
|
4
|
+
Este módulo fornece uma função única `setup_logger()` utilizada por todo
|
|
5
|
+
o ecossistema de plugins do mtcli para configurar logging consistente.
|
|
6
|
+
|
|
7
|
+
Características principais
|
|
8
|
+
--------------------------
|
|
9
|
+
|
|
10
|
+
✔ Arquivo de log rotativo em:
|
|
11
|
+
%APPDATA%/mtcli/logs/mtcli.log
|
|
12
|
+
|
|
13
|
+
✔ Rotação automática:
|
|
14
|
+
- tamanho máximo: 2 MB
|
|
15
|
+
- até 3 arquivos de backup
|
|
16
|
+
|
|
17
|
+
✔ Proteção contra duplicação de handlers quando plugins
|
|
18
|
+
inicializam o logger múltiplas vezes.
|
|
19
|
+
|
|
20
|
+
✔ Encoding UTF-8 garantido (evita problemas de acentuação
|
|
21
|
+
no Windows).
|
|
22
|
+
|
|
23
|
+
✔ Compatível com pytest (caplog).
|
|
24
|
+
|
|
25
|
+
Observação
|
|
26
|
+
----------
|
|
27
|
+
|
|
28
|
+
Os logs **não são exibidos no console**.
|
|
29
|
+
Toda saída é direcionada exclusivamente para o arquivo de log.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
from logging.handlers import RotatingFileHandler
|
|
34
|
+
import os
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ==========================================================
|
|
38
|
+
# DIRETÓRIO DE LOG
|
|
39
|
+
# ==========================================================
|
|
40
|
+
|
|
41
|
+
base_dir = os.getenv("APPDATA", os.path.expanduser("~"))
|
|
42
|
+
|
|
43
|
+
LOG_DIR = os.path.join(base_dir, "mtcli", "logs")
|
|
44
|
+
|
|
45
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
LOG_FILE = os.path.join(LOG_DIR, "mtcli.log")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ==========================================================
|
|
51
|
+
# LOGGER SETUP
|
|
52
|
+
# ==========================================================
|
|
53
|
+
|
|
54
|
+
def setup_logger(name: str = "mtcli") -> logging.Logger:
|
|
55
|
+
"""
|
|
56
|
+
Cria ou retorna um logger configurado para o mtcli.
|
|
57
|
+
|
|
58
|
+
O logger utiliza **apenas um handler de arquivo rotativo**.
|
|
59
|
+
Nenhuma saída é enviada ao console.
|
|
60
|
+
|
|
61
|
+
A função é **idempotente**, ou seja, pode ser chamada
|
|
62
|
+
múltiplas vezes sem duplicar handlers.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
name : str
|
|
67
|
+
Nome do logger (normalmente `__name__`).
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
logging.Logger
|
|
72
|
+
Instância configurada do logger.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
logger = logging.getLogger(name)
|
|
76
|
+
|
|
77
|
+
logger.setLevel(logging.DEBUG)
|
|
78
|
+
|
|
79
|
+
formatter = logging.Formatter(
|
|
80
|
+
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
|
81
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# ======================================================
|
|
85
|
+
# REMOVE STREAM HANDLERS (garante silêncio no console)
|
|
86
|
+
# ======================================================
|
|
87
|
+
|
|
88
|
+
for handler in list(logger.handlers):
|
|
89
|
+
if isinstance(handler, logging.StreamHandler) and not isinstance(handler, RotatingFileHandler):
|
|
90
|
+
logger.removeHandler(handler)
|
|
91
|
+
|
|
92
|
+
# ======================================================
|
|
93
|
+
# FILE HANDLER ROTATIVO
|
|
94
|
+
# ======================================================
|
|
95
|
+
|
|
96
|
+
file_handler_exists = any(
|
|
97
|
+
isinstance(h, RotatingFileHandler) for h in logger.handlers
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not file_handler_exists:
|
|
101
|
+
|
|
102
|
+
file_handler = RotatingFileHandler(
|
|
103
|
+
LOG_FILE,
|
|
104
|
+
maxBytes=2_000_000,
|
|
105
|
+
backupCount=3,
|
|
106
|
+
encoding="utf-8",
|
|
107
|
+
delay=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
file_handler.setFormatter(formatter)
|
|
111
|
+
|
|
112
|
+
logger.addHandler(file_handler)
|
|
113
|
+
|
|
114
|
+
# ======================================================
|
|
115
|
+
# PROPAGATION
|
|
116
|
+
# ======================================================
|
|
117
|
+
|
|
118
|
+
# Permite que pytest caplog capture logs
|
|
119
|
+
logger.propagate = True
|
|
120
|
+
|
|
121
|
+
return logger
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ==========================================================
|
|
125
|
+
# LOGGER PADRÃO DO MTCLI
|
|
126
|
+
# ==========================================================
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
Logger padrão utilizado por módulos internos do mtcli.
|
|
130
|
+
|
|
131
|
+
Plugins geralmente criam seu próprio logger usando:
|
|
132
|
+
|
|
133
|
+
setup_logger(__name__)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
log = setup_logger()
|