mtcli 3.7.0.dev9__tar.gz → 3.8.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 (82) hide show
  1. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/PKG-INFO +3 -3
  2. mtcli-3.8.0.dev0/mtcli/cli.py +135 -0
  3. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/commands/conf.py +44 -44
  4. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/commands/doctor.py +194 -194
  5. mtcli-3.8.0.dev0/mtcli/commands/migrate.py +12 -0
  6. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/conf.py +309 -309
  7. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/config_registre.py +41 -41
  8. mtcli-3.8.0.dev0/mtcli/database.py +66 -0
  9. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/logger.py +136 -136
  10. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/marketdata/tick_cache.py +38 -24
  11. mtcli-3.8.0.dev0/mtcli/marketdata/tick_repository.py +175 -0
  12. mtcli-3.8.0.dev0/mtcli/marketdata/tick_streamer.py +75 -0
  13. mtcli-3.8.0.dev0/mtcli/migrations/001_initial_schema.py +21 -0
  14. mtcli-3.8.0.dev0/mtcli/migrations/002_ticks_time_msc.py +21 -0
  15. mtcli-3.8.0.dev0/mtcli/migrations/003_optimize_ticks_without_rowid.py +63 -0
  16. mtcli-3.8.0.dev0/mtcli/migrations/runner.py +104 -0
  17. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/rates_model.py +41 -41
  18. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugin_loader.py +123 -123
  19. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugin_manager.py +27 -27
  20. mtcli-3.8.0.dev0/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  21. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/pyproject.toml +4 -4
  22. mtcli-3.7.0.dev9/mtcli/cli.py +0 -73
  23. mtcli-3.7.0.dev9/mtcli/database.py +0 -54
  24. mtcli-3.7.0.dev9/mtcli/marketdata/tick_repository.py +0 -142
  25. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/LICENSE +0 -0
  26. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/README.md +0 -0
  27. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/__init__.py +0 -0
  28. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/commands/__init__.py +0 -0
  29. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/commands/bars.py +0 -0
  30. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/conecta.py +0 -0
  31. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/data/__init__.py +0 -0
  32. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/data/base.py +0 -0
  33. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/data/csv.py +0 -0
  34. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/data/mt5.py +0 -0
  35. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/domain/__init__.py +0 -0
  36. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/domain/timeframe.py +0 -0
  37. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/marketdata/__init__.py +0 -0
  38. {mtcli-3.7.0.dev9/mtcli/plugins → mtcli-3.8.0.dev0/mtcli/migrations}/__init__.py +0 -0
  39. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/__init__.py +0 -0
  40. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/bar_model.py +0 -0
  41. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/bars_model.py +0 -0
  42. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/chart_model.py +0 -0
  43. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/conf_model.py +0 -0
  44. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/consecutive_bars_model.py +0 -0
  45. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/signals_model.py +0 -0
  46. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/models/unconsecutive_bar_model.py +0 -0
  47. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/mt5_context.py +0 -0
  48. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugin.py +0 -0
  49. {mtcli-3.7.0.dev9/mtcli/plugins/media_movel/tests → mtcli-3.8.0.dev0/mtcli/plugins}/__init__.py +0 -0
  50. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/exemplo.py-dist +0 -0
  51. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/__init__.py +0 -0
  52. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/cli.py +0 -0
  53. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/conf.py +0 -0
  54. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  55. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  56. {mtcli-3.7.0.dev9/mtcli/plugins/range_medio/models → mtcli-3.8.0.dev0/mtcli/plugins/media_movel/tests}/__init__.py +0 -0
  57. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  58. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  59. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/range_medio/__init__.py +0 -0
  60. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/range_medio/cli.py +0 -0
  61. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/range_medio/conf.py +0 -0
  62. {mtcli-3.7.0.dev9/mtcli/plugins/range_medio/tests → mtcli-3.8.0.dev0/mtcli/plugins/range_medio/models}/__init__.py +0 -0
  63. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  64. {mtcli-3.7.0.dev9/mtcli/plugins/volume_medio → mtcli-3.8.0.dev0/mtcli/plugins/range_medio}/tests/__init__.py +0 -0
  65. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  66. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/__init__.py +0 -0
  67. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/cli.py +0 -0
  68. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/conf.py +0 -0
  69. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  70. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  71. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  72. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/__init__.py +0 -0
  73. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/close_view.py +0 -0
  74. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/full_view.py +0 -0
  75. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/high_view.py +0 -0
  76. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/low_view.py +0 -0
  77. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/min_view.py +0 -0
  78. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/open_view.py +0 -0
  79. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/ranges_view.py +0 -0
  80. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/rates_view.py +0 -0
  81. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/vars_view.py +0 -0
  82. {mtcli-3.7.0.dev9 → mtcli-3.8.0.dev0}/mtcli/views/volumes_view.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli
3
- Version: 3.7.0.dev9
3
+ Version: 3.8.0.dev0
4
4
  Summary: Aplicativo CLI para exibir gráficos do MetaTrader 5 screen reader friendly
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
7
7
  Keywords: trading,candlestick,metatrader5,mt5,cli,price-action
8
- Author: Valmir França da Silva
8
+ Author: Valmir França
9
9
  Author-email: vfranca3@gmail.com
10
10
  Requires-Python: >=3.10,<3.14.0
11
- Classifier: Development Status :: 4 - Beta
11
+ Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Financial and Insurance Industry
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.10
@@ -0,0 +1,135 @@
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
+ Também inicia a captura contínua de ticks para manter
8
+ um histórico próprio independente do broker.
9
+ """
10
+
11
+ import os
12
+ import threading
13
+ import click
14
+
15
+ from mtcli.plugin_loader import load_plugins
16
+ from mtcli.logger import setup_logger
17
+ from mtcli.marketdata.tick_streamer import TickStreamer
18
+
19
+ from .commands.bars import bars
20
+ from .commands.doctor import doctor
21
+ from .commands.migrate import migrate
22
+
23
+
24
+ logger = setup_logger(__name__)
25
+
26
+
27
+ # ---------------------------------------------------------
28
+ # Configuração de captura de ticks
29
+ # ---------------------------------------------------------
30
+
31
+ _tick_streamer = None
32
+
33
+
34
+ def start_tick_capture():
35
+ """
36
+ Inicia captura contínua de ticks em background.
37
+
38
+ O símbolo pode ser configurado via variável de ambiente:
39
+
40
+ MTCLI_SYMBOL=WINJ26
41
+ """
42
+
43
+ global _tick_streamer
44
+
45
+ if _tick_streamer:
46
+ return
47
+
48
+ symbol = os.getenv("SYMBOL")
49
+
50
+ if not symbol:
51
+ logger.info("Captura de ticks desativada (MTCLI_SYMBOL não definido).")
52
+ return
53
+
54
+ logger.info("Iniciando captura contínua de ticks para %s", symbol)
55
+
56
+ _tick_streamer = TickStreamer(symbol)
57
+
58
+ thread = threading.Thread(
59
+ target=_tick_streamer.start,
60
+ daemon=True,
61
+ name="mtcli-tick-streamer",
62
+ )
63
+
64
+ thread.start()
65
+
66
+ logger.info("Captura de ticks iniciada em background.")
67
+
68
+
69
+ # ---------------------------------------------------------
70
+ # CLI principal
71
+ # ---------------------------------------------------------
72
+
73
+ @click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
74
+ @click.version_option(package_name="mtcli")
75
+ @click.pass_context
76
+ def mt(ctx):
77
+ """
78
+ CLI principal do mtcli.
79
+
80
+ Exibe gráficos e informações de mercado
81
+ em formato textual compatível com leitores de tela.
82
+ """
83
+
84
+ # inicia captura de ticks
85
+ start_tick_capture()
86
+
87
+ if ctx.invoked_subcommand is None:
88
+ click.echo(ctx.get_help())
89
+
90
+
91
+ # ---------------------------------------------------------
92
+ # Comandos internos
93
+ # ---------------------------------------------------------
94
+
95
+ mt.add_command(doctor, name="doctor")
96
+ mt.add_command(bars, name="bars")
97
+ mt.add_command(doctor, name="dr")
98
+ mt.add_command(migrate)
99
+
100
+
101
+ # ---------------------------------------------------------
102
+ # Carregamento de plugins
103
+ # ---------------------------------------------------------
104
+
105
+ loaded_plugins = load_plugins(mt)
106
+
107
+ logger.info("Plugins carregados: %s", loaded_plugins)
108
+
109
+
110
+ # ---------------------------------------------------------
111
+ # Comando utilitário: listar plugins
112
+ # ---------------------------------------------------------
113
+
114
+ @mt.command(name="plugins")
115
+ def list_plugins():
116
+ """
117
+ Lista os plugins atualmente carregados no mtcli.
118
+ """
119
+
120
+ if not loaded_plugins:
121
+ click.echo("Nenhum plugin carregado.")
122
+ return
123
+
124
+ click.echo("Plugins carregados:\n")
125
+
126
+ for name in loaded_plugins:
127
+ click.echo(f" {name}")
128
+
129
+
130
+ # ---------------------------------------------------------
131
+ # Entry point
132
+ # ---------------------------------------------------------
133
+
134
+ if __name__ == "__main__":
135
+ mt()
@@ -1,44 +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()
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()
@@ -1,194 +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.")
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,12 @@
1
+ import click
2
+
3
+ from mtcli.database import get_connection
4
+ from mtcli.migrations.runner import run_migrations
5
+
6
+
7
+ @click.command()
8
+ def migrate():
9
+
10
+ conn = get_connection()
11
+
12
+ run_migrations(conn)