mtcli 3.6.0.dev1__tar.gz → 3.7.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.
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/PKG-INFO +1 -1
- mtcli-3.7.0/mtcli/cli.py +74 -0
- mtcli-3.7.0/mtcli/commands/conf.py +44 -0
- mtcli-3.7.0/mtcli/commands/doctor.py +194 -0
- mtcli-3.7.0/mtcli/conf.py +309 -0
- mtcli-3.7.0/mtcli/config_registre.py +41 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/data/csv.py +31 -31
- mtcli-3.7.0/mtcli/logger.py +136 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/consecutive_bars_model.py +77 -77
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/rates_model.py +41 -41
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/unconsecutive_bar_model.py +67 -67
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugin.py +18 -18
- mtcli-3.7.0/mtcli/plugin_loader.py +123 -0
- mtcli-3.7.0/mtcli/plugin_manager.py +27 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/__init__.py +5 -5
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/tests/test_mm.py +13 -13
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/cli.py +32 -32
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/models/average_range_model.py +29 -29
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/tests/test_rm.py +11 -11
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/cli.py +41 -41
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/models/model_average_volume.py +31 -31
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/tests/test_vm.py +21 -21
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/close_view.py +37 -37
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/full_view.py +65 -65
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/high_view.py +37 -37
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/low_view.py +37 -37
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/min_view.py +42 -42
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/open_view.py +37 -37
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/ranges_view.py +41 -41
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/rates_view.py +41 -41
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/pyproject.toml +2 -2
- mtcli-3.6.0.dev1/mtcli/cli.py +0 -34
- mtcli-3.6.0.dev1/mtcli/conf.py +0 -199
- mtcli-3.6.0.dev1/mtcli/logger.py +0 -47
- mtcli-3.6.0.dev1/mtcli/plugin_loader.py +0 -97
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/LICENSE +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/README.md +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/commands/bars.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/conecta.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/data/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/data/base.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/data/mt5.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/database.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/marketdata/tick_repository.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/mt5_context.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/__init__.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.6.0.dev1 → mtcli-3.7.0}/mtcli/views/volumes_view.py +0 -0
mtcli-3.7.0/mtcli/cli.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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 mtcli.logger import setup_logger
|
|
12
|
+
|
|
13
|
+
from .commands.bars import bars
|
|
14
|
+
from .commands.doctor import doctor
|
|
15
|
+
|
|
16
|
+
logger = setup_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group(context_settings={"max_content_width": 120})
|
|
20
|
+
@click.version_option(package_name="mtcli")
|
|
21
|
+
def mt():
|
|
22
|
+
"""
|
|
23
|
+
CLI principal do mtcli.
|
|
24
|
+
|
|
25
|
+
Exibe gráficos e informações de mercado
|
|
26
|
+
em formato textual compatível com leitores de tela.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------
|
|
32
|
+
# Comandos internos
|
|
33
|
+
# ---------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
mt.add_command(doctor, name="doctor")
|
|
36
|
+
mt.add_command(bars, name="bars")
|
|
37
|
+
mt.add_command(doctor, name="dr")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------
|
|
41
|
+
# Carregamento de plugins
|
|
42
|
+
# ---------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
loaded_plugins = load_plugins(mt)
|
|
45
|
+
|
|
46
|
+
logger.info("Plugins carregados: %s", loaded_plugins)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------
|
|
50
|
+
# Comando utilitário: listar plugins
|
|
51
|
+
# ---------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
@mt.command(name="plugins")
|
|
54
|
+
def list_plugins():
|
|
55
|
+
"""
|
|
56
|
+
Lista os plugins atualmente carregados no mtcli.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
if not loaded_plugins:
|
|
60
|
+
click.echo("Nenhum plugin carregado.")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
click.echo("Plugins carregados:\n")
|
|
64
|
+
|
|
65
|
+
for name in loaded_plugins:
|
|
66
|
+
click.echo(f" {name}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------
|
|
70
|
+
# Entry point
|
|
71
|
+
# ---------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
mt()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comando para exibir configurações disponíveis.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from mtcli.config_registry import registry
|
|
8
|
+
from mtcli.conf import conf
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("section", required=False)
|
|
13
|
+
def conf_cmd(section):
|
|
14
|
+
"""
|
|
15
|
+
Exibe configurações disponíveis.
|
|
16
|
+
|
|
17
|
+
Exemplos:
|
|
18
|
+
|
|
19
|
+
mt conf
|
|
20
|
+
mt conf renko
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
if section:
|
|
24
|
+
options = registry.get_section(section)
|
|
25
|
+
else:
|
|
26
|
+
options = registry.get_all()
|
|
27
|
+
|
|
28
|
+
if not options:
|
|
29
|
+
click.echo("Nenhuma configuração registrada.")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
for opt in options:
|
|
33
|
+
|
|
34
|
+
value = conf.get(opt.name, section=opt.section, default=opt.default)
|
|
35
|
+
|
|
36
|
+
click.echo(
|
|
37
|
+
f"{opt.section}.{opt.name} = {value} "
|
|
38
|
+
f"(default={opt.default})"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if opt.description:
|
|
42
|
+
click.echo(f" {opt.description}")
|
|
43
|
+
|
|
44
|
+
click.echo()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comando de diagnóstico do mtcli.
|
|
3
|
+
|
|
4
|
+
Este comando executa uma série de verificações básicas para ajudar
|
|
5
|
+
a identificar problemas de instalação, configuração ou carregamento
|
|
6
|
+
de plugins no ambiente do mtcli.
|
|
7
|
+
|
|
8
|
+
As verificações incluem:
|
|
9
|
+
|
|
10
|
+
- Versão do Python
|
|
11
|
+
- Instalação do mtcli
|
|
12
|
+
- Fonte de dados configurada
|
|
13
|
+
- Caminho de dados CSV
|
|
14
|
+
- Conexão com MetaTrader5
|
|
15
|
+
- Descoberta e carregamento de plugins
|
|
16
|
+
|
|
17
|
+
Caso algum plugin falhe ao carregar, o erro é exibido e registrado
|
|
18
|
+
no sistema de logs.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
import click
|
|
23
|
+
import traceback
|
|
24
|
+
|
|
25
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
26
|
+
|
|
27
|
+
import MetaTrader5 as mt5
|
|
28
|
+
|
|
29
|
+
from mtcli.logger import setup_logger
|
|
30
|
+
from mtcli.conf import conf, DATA_SOURCE_NAME
|
|
31
|
+
from mtcli.mt5_context import mt5_conexao
|
|
32
|
+
from mtcli.plugin_loader import discover_plugins
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
logger = setup_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def status(ok: bool) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Retorna um marcador visual simples de status.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
ok : bool
|
|
45
|
+
Indica se o teste foi bem sucedido.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
str
|
|
50
|
+
Texto representando o status.
|
|
51
|
+
"""
|
|
52
|
+
return "OK " if ok else "ERRO"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@click.command()
|
|
56
|
+
def doctor():
|
|
57
|
+
"""
|
|
58
|
+
Executa diagnóstico do ambiente mtcli.
|
|
59
|
+
|
|
60
|
+
O comando verifica se os componentes essenciais do ambiente
|
|
61
|
+
estão funcionando corretamente e apresenta um relatório simples
|
|
62
|
+
no terminal.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
click.echo("Diagnóstico do ambiente mtcli\n")
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------
|
|
68
|
+
# Python
|
|
69
|
+
# ---------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
python_ok = sys.version_info >= (3, 10)
|
|
72
|
+
|
|
73
|
+
click.echo(f"{status(python_ok)} Python: {sys.version.split()[0]}")
|
|
74
|
+
|
|
75
|
+
if not python_ok:
|
|
76
|
+
click.echo(" Versão mínima recomendada: Python 3.10")
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------
|
|
79
|
+
# mtcli
|
|
80
|
+
# ---------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
|
|
84
|
+
mtcli_version = version("mtcli")
|
|
85
|
+
|
|
86
|
+
click.echo(f"{status(True)} mtcli: {mtcli_version}")
|
|
87
|
+
|
|
88
|
+
except PackageNotFoundError:
|
|
89
|
+
|
|
90
|
+
click.echo(f"{status(False)} mtcli não encontrado")
|
|
91
|
+
|
|
92
|
+
logger.error("Pacote mtcli não encontrado no ambiente.")
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------
|
|
95
|
+
# Fonte de dados
|
|
96
|
+
# ---------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
click.echo(f"{status(True)} Data source: {DATA_SOURCE_NAME}")
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------
|
|
101
|
+
# CSV path
|
|
102
|
+
# ---------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
|
|
106
|
+
path = conf.get_csv_path()
|
|
107
|
+
|
|
108
|
+
click.echo(f"{status(True)} CSV path: {path}")
|
|
109
|
+
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
|
|
112
|
+
click.echo(f"{status(False)} CSV path inválido")
|
|
113
|
+
|
|
114
|
+
logger.exception("Erro ao obter CSV path: %s", exc)
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------
|
|
117
|
+
# MT5 conexão
|
|
118
|
+
# ---------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
|
|
122
|
+
with mt5_conexao():
|
|
123
|
+
|
|
124
|
+
info = mt5.terminal_info()
|
|
125
|
+
|
|
126
|
+
if info:
|
|
127
|
+
|
|
128
|
+
click.echo(f"{status(True)} MetaTrader5 conectado")
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
|
|
132
|
+
click.echo(f"{status(False)} MetaTrader5 não respondeu")
|
|
133
|
+
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
|
|
136
|
+
click.echo(f"{status(False)} Falha na conexão MT5")
|
|
137
|
+
|
|
138
|
+
logger.exception("Erro ao conectar MT5: %s", exc)
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------
|
|
141
|
+
# Plugins
|
|
142
|
+
# ---------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
click.echo("\nPlugins:")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
|
|
148
|
+
entry_points = list(discover_plugins())
|
|
149
|
+
|
|
150
|
+
if not entry_points:
|
|
151
|
+
|
|
152
|
+
click.echo(" Nenhum plugin encontrado.")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
ok_count = 0
|
|
156
|
+
fail_count = 0
|
|
157
|
+
|
|
158
|
+
for ep in entry_points:
|
|
159
|
+
|
|
160
|
+
plugin_name = ep.name
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
|
|
164
|
+
ep.load()
|
|
165
|
+
|
|
166
|
+
click.echo(f" OK {plugin_name}")
|
|
167
|
+
|
|
168
|
+
ok_count += 1
|
|
169
|
+
|
|
170
|
+
except Exception as exc:
|
|
171
|
+
|
|
172
|
+
click.echo(f" ERRO {plugin_name}")
|
|
173
|
+
|
|
174
|
+
click.echo(f" {exc}")
|
|
175
|
+
|
|
176
|
+
logger.error(
|
|
177
|
+
"Erro ao carregar plugin %s\n%s",
|
|
178
|
+
plugin_name,
|
|
179
|
+
traceback.format_exc(),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
fail_count += 1
|
|
183
|
+
|
|
184
|
+
click.echo(
|
|
185
|
+
f"\nResumo: {ok_count} plugins OK | {fail_count} com erro"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
|
|
190
|
+
click.echo(f"{status(False)} Falha ao descobrir plugins")
|
|
191
|
+
|
|
192
|
+
logger.exception("Erro ao descobrir plugins: %s", exc)
|
|
193
|
+
|
|
194
|
+
click.echo("\nDiagnóstico concluído.")
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sistema central de configuração do mtcli.
|
|
3
|
+
|
|
4
|
+
Fornece leitura de configuração a partir de:
|
|
5
|
+
|
|
6
|
+
1. Variáveis de ambiente
|
|
7
|
+
2. Arquivo mtcli.ini
|
|
8
|
+
3. Valores default
|
|
9
|
+
|
|
10
|
+
Também oferece utilidades usadas por plugins como:
|
|
11
|
+
|
|
12
|
+
- descoberta do diretório MQL5/Files
|
|
13
|
+
- seleção da fonte de dados (CSV ou MT5)
|
|
14
|
+
|
|
15
|
+
Plugins novos devem acessar a configuração através do objeto global `conf`.
|
|
16
|
+
|
|
17
|
+
Compatibilidade retroativa:
|
|
18
|
+
---------------------------
|
|
19
|
+
Plugins antigos utilizavam:
|
|
20
|
+
|
|
21
|
+
from mtcli.conf import config
|
|
22
|
+
|
|
23
|
+
onde `config` era um objeto `configparser.ConfigParser`.
|
|
24
|
+
|
|
25
|
+
Para manter compatibilidade com plugins já publicados,
|
|
26
|
+
o objeto `config` continua sendo exposto.
|
|
27
|
+
|
|
28
|
+
Essa API é considerada **deprecated** e poderá ser removida
|
|
29
|
+
em versões futuras do mtcli.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
import configparser
|
|
34
|
+
|
|
35
|
+
import MetaTrader5 as mt5
|
|
36
|
+
|
|
37
|
+
from mtcli.mt5_context import mt5_conexao
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
"""
|
|
42
|
+
Gerenciador central de configurações do mtcli.
|
|
43
|
+
|
|
44
|
+
Permite acessar valores a partir de:
|
|
45
|
+
|
|
46
|
+
- variáveis de ambiente
|
|
47
|
+
- arquivo mtcli.ini
|
|
48
|
+
- valores default
|
|
49
|
+
|
|
50
|
+
Plugins devem preferencialmente usar:
|
|
51
|
+
|
|
52
|
+
from mtcli.conf import conf
|
|
53
|
+
conf.get(...)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, filename="mtcli.ini"):
|
|
57
|
+
self.config = configparser.ConfigParser()
|
|
58
|
+
self.config.read(filename)
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------
|
|
61
|
+
# leitura de valores
|
|
62
|
+
# ---------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def get(self, key, section="DEFAULT", cast=None, default=None):
|
|
65
|
+
"""
|
|
66
|
+
Retorna um valor de configuração.
|
|
67
|
+
|
|
68
|
+
Prioridade:
|
|
69
|
+
|
|
70
|
+
1. Variável de ambiente SECTION_KEY
|
|
71
|
+
2. Variável de ambiente KEY
|
|
72
|
+
3. mtcli.ini [section]
|
|
73
|
+
4. mtcli.ini [DEFAULT]
|
|
74
|
+
5. default
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
key (str):
|
|
78
|
+
Nome da configuração.
|
|
79
|
+
|
|
80
|
+
section (str):
|
|
81
|
+
Seção do arquivo mtcli.ini.
|
|
82
|
+
|
|
83
|
+
cast (type | None):
|
|
84
|
+
Tipo para conversão do valor.
|
|
85
|
+
|
|
86
|
+
default (Any):
|
|
87
|
+
Valor padrão.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Any
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
env_key = f"{section.upper()}_{key.upper()}"
|
|
94
|
+
|
|
95
|
+
value = os.getenv(env_key) or os.getenv(key.upper())
|
|
96
|
+
|
|
97
|
+
if value is None:
|
|
98
|
+
|
|
99
|
+
if self.config.has_option(section, key):
|
|
100
|
+
value = self.config.get(section, key)
|
|
101
|
+
|
|
102
|
+
elif self.config.has_option("DEFAULT", key):
|
|
103
|
+
value = self.config.get("DEFAULT", key)
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
value = default
|
|
107
|
+
|
|
108
|
+
if cast and value is not None:
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
|
|
112
|
+
if cast is bool:
|
|
113
|
+
value = str(value).lower() in ("1", "true", "yes")
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
value = cast(value)
|
|
117
|
+
|
|
118
|
+
except ValueError:
|
|
119
|
+
value = default
|
|
120
|
+
|
|
121
|
+
return value
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------
|
|
124
|
+
# seção helper
|
|
125
|
+
# ---------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
def section(self, section):
|
|
128
|
+
"""
|
|
129
|
+
Retorna um helper para acessar uma seção específica.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
|
|
133
|
+
renko = conf.section("renko")
|
|
134
|
+
brick = renko.get("brick", cast=int, default=10)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
class Section:
|
|
138
|
+
def __init__(self, parent, section):
|
|
139
|
+
self.parent = parent
|
|
140
|
+
self.section = section
|
|
141
|
+
|
|
142
|
+
def get(self, key, cast=None, default=None):
|
|
143
|
+
return self.parent.get(key, self.section, cast, default)
|
|
144
|
+
|
|
145
|
+
return Section(self, section)
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------
|
|
148
|
+
# caminho MT5
|
|
149
|
+
# ---------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
def get_csv_path(self):
|
|
152
|
+
"""
|
|
153
|
+
Retorna o caminho da pasta MQL5/Files do MetaTrader 5.
|
|
154
|
+
|
|
155
|
+
A prioridade é:
|
|
156
|
+
|
|
157
|
+
1. mtcli.ini -> mt5_pasta
|
|
158
|
+
2. descoberta automática via MT5
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str: caminho normalizado da pasta Files.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
path = self.get("mt5_pasta")
|
|
165
|
+
|
|
166
|
+
if path:
|
|
167
|
+
return os.path.normpath(path) + os.sep
|
|
168
|
+
|
|
169
|
+
with mt5_conexao():
|
|
170
|
+
|
|
171
|
+
info = mt5.terminal_info()
|
|
172
|
+
|
|
173
|
+
if info is None:
|
|
174
|
+
raise RuntimeError(
|
|
175
|
+
"Não foi possível obter informações do terminal MT5."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
path = os.path.join(info.data_path, "MQL5", "Files")
|
|
179
|
+
|
|
180
|
+
return os.path.normpath(path) + os.sep
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------
|
|
183
|
+
# data source
|
|
184
|
+
# ---------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
def get_data_source(self, source=None):
|
|
187
|
+
"""
|
|
188
|
+
Retorna a fonte de dados configurada.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
source (str | None):
|
|
192
|
+
Fonte explícita ("csv" ou "mt5").
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
DataSource
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
from mtcli.data import CsvDataSource, MT5DataSource
|
|
199
|
+
|
|
200
|
+
src = (source or self.get("dados", default="mt5")).lower()
|
|
201
|
+
|
|
202
|
+
if src == "csv":
|
|
203
|
+
return CsvDataSource()
|
|
204
|
+
|
|
205
|
+
if src == "mt5":
|
|
206
|
+
return MT5DataSource()
|
|
207
|
+
|
|
208
|
+
raise ValueError(f"Fonte de dados desconhecida: {src}")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------
|
|
212
|
+
# instância global usada por todo o sistema
|
|
213
|
+
# ---------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
conf = Config()
|
|
216
|
+
|
|
217
|
+
# ---------------------------------------------------------
|
|
218
|
+
# compatibilidade com plugins antigos
|
|
219
|
+
# ---------------------------------------------------------
|
|
220
|
+
#
|
|
221
|
+
# Plugins antigos utilizam:
|
|
222
|
+
#
|
|
223
|
+
# from mtcli.conf import config
|
|
224
|
+
#
|
|
225
|
+
# onde `config` era um ConfigParser.
|
|
226
|
+
#
|
|
227
|
+
# Mantemos esse objeto apontando para o ConfigParser interno
|
|
228
|
+
# para evitar quebra de compatibilidade.
|
|
229
|
+
#
|
|
230
|
+
# API DEPRECATED – usar `conf.get()` em novos plugins.
|
|
231
|
+
#
|
|
232
|
+
|
|
233
|
+
config = conf.config
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------
|
|
237
|
+
# timeframes suportados
|
|
238
|
+
# ---------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
_HOURS = [12, 8, 6, 4, 3, 2, 1]
|
|
241
|
+
_MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
|
|
242
|
+
|
|
243
|
+
TIMEFRAMES = (
|
|
244
|
+
["mn1", "w1", "d1"]
|
|
245
|
+
+ [f"h{i}" for i in _HOURS]
|
|
246
|
+
+ [f"m{i}" for i in _MINUTES]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# ---------------------------------------------------------
|
|
250
|
+
# Configurações gerais
|
|
251
|
+
# ---------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
SYMBOL = conf.get("symbol", default="WIN$N")
|
|
254
|
+
DIGITOS = conf.get("digitos", cast=int, default=2)
|
|
255
|
+
PERIOD = conf.get("period", default="D1")
|
|
256
|
+
BARS = conf.get("count", cast=int, default=999)
|
|
257
|
+
|
|
258
|
+
VIEW = conf.get("view", default="ch")
|
|
259
|
+
VOLUME = conf.get("volume", default="tick")
|
|
260
|
+
DATE = conf.get("date", default="")
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------
|
|
263
|
+
# Configurações de leitura de candles
|
|
264
|
+
# ---------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
LATERAL = conf.get("lateral", default="doji")
|
|
267
|
+
ALTA = conf.get("alta", default="verde")
|
|
268
|
+
BAIXA = conf.get("baixa", default="vermelho")
|
|
269
|
+
|
|
270
|
+
ROMPIMENTO_ALTA = conf.get("rompimento_alta", default="c")
|
|
271
|
+
ROMPIMENTO_BAIXA = conf.get("rompimento_baixa", default="v")
|
|
272
|
+
|
|
273
|
+
PERCENTUAL_ROMPIMENTO = conf.get(
|
|
274
|
+
"percentual_rompimento",
|
|
275
|
+
cast=int,
|
|
276
|
+
default=50,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
PERCENTUAL_DOJI = conf.get(
|
|
280
|
+
"percentual_doji",
|
|
281
|
+
cast=int,
|
|
282
|
+
default=10,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------
|
|
286
|
+
# Configurações de padrões de barra
|
|
287
|
+
# ---------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
UP_BAR = conf.get("up_bar", default="asc")
|
|
290
|
+
DOWN_BAR = conf.get("down_bar", default="desc")
|
|
291
|
+
|
|
292
|
+
INSIDE_BAR = conf.get("inside_bar", default="ib")
|
|
293
|
+
OUTSIDE_BAR = conf.get("outside_bar", default="ob")
|
|
294
|
+
|
|
295
|
+
SOMBRA_SUPERIOR = conf.get("sombra_superior", default="top")
|
|
296
|
+
SOMBRA_INFERIOR = conf.get("sombra_inferior", default="bottom")
|
|
297
|
+
|
|
298
|
+
# ---------------------------------------------------------
|
|
299
|
+
# Fonte de dados
|
|
300
|
+
# ---------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
DATA_SOURCE_NAME = conf.get("dados", default="mt5").lower()
|
|
303
|
+
DATA_SOURCE = conf.get_data_source()
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------
|
|
306
|
+
# caminho inicial do CSV (pode vir do ini/env)
|
|
307
|
+
# ---------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
_INITIAL_CSV_PATH = conf.get_csv_path()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Registry de configurações do mtcli.
|
|
3
|
+
|
|
4
|
+
Plugins podem registrar suas opções de configuração aqui
|
|
5
|
+
para permitir descoberta automática via CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigOption:
|
|
10
|
+
"""
|
|
11
|
+
Representa uma opção de configuração registrada.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, section, name, type=str, default=None, description=""):
|
|
15
|
+
self.section = section
|
|
16
|
+
self.name = name
|
|
17
|
+
self.type = type
|
|
18
|
+
self.default = default
|
|
19
|
+
self.description = description
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConfigRegistry:
|
|
23
|
+
"""
|
|
24
|
+
Registro central de opções de configuração.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self._options = []
|
|
29
|
+
|
|
30
|
+
def register(self, section, name, type=str, default=None, description=""):
|
|
31
|
+
option = ConfigOption(section, name, type, default, description)
|
|
32
|
+
self._options.append(option)
|
|
33
|
+
|
|
34
|
+
def get_all(self):
|
|
35
|
+
return self._options
|
|
36
|
+
|
|
37
|
+
def get_section(self, section):
|
|
38
|
+
return [o for o in self._options if o.section == section]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
registry = ConfigRegistry()
|