mtcli 4.0.0.dev0__py3-none-any.whl → 4.0.0.dev1__py3-none-any.whl
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/__main__.py +3 -0
- mtcli/cli.py +51 -0
- mtcli/commands/bars.py +75 -153
- mtcli/commands/conf.py +35 -41
- mtcli/commands/doctor.py +194 -0
- mtcli/conf.py +125 -96
- mtcli/config_registre.py +41 -0
- mtcli/controllers/__init__.py +0 -0
- mtcli/controllers/bars_controller.py +75 -0
- mtcli/data/base.py +20 -9
- mtcli/data/csv.py +63 -31
- mtcli/data/factory.py +29 -0
- mtcli/data/mt5.py +123 -70
- mtcli/domain/__init__.py +0 -0
- mtcli/domain/bar.py +40 -0
- mtcli/domain/calculations.py +6 -0
- mtcli/domain/config.py +4 -0
- mtcli/domain/pattern.py +10 -0
- mtcli/domain/price.py +13 -0
- mtcli/domain/result.py +8 -0
- mtcli/domain/session.py +11 -0
- mtcli/domain/structure.py +7 -0
- mtcli/domain/symbol.py +10 -0
- mtcli/domain/tick.py +9 -0
- mtcli/domain/timeframe.py +102 -0
- mtcli/logger.py +136 -46
- mtcli/models/bars_model.py +175 -21
- mtcli/models/rate_model.py +71 -0
- mtcli/models_legado/__init__.py +1 -0
- mtcli/{models → models_legado}/bar_model.py +105 -105
- mtcli/models_legado/bars_model.py +25 -0
- mtcli/models_legado/chart.py +26 -0
- mtcli/models_legado/consecutive.py +29 -0
- mtcli/{models → models_legado}/rates_model.py +1 -1
- mtcli/plugin.py +18 -9
- mtcli/plugin_loader.py +123 -0
- mtcli/plugin_manager.py +27 -0
- mtcli/plugins/exemplo.py-dist +30 -0
- mtcli/plugins/media_movel/__init__.py +5 -3
- mtcli/plugins/media_movel/cli.py +73 -0
- mtcli/plugins/media_movel/conf.py +75 -4
- mtcli/plugins/media_movel/controller.py +43 -0
- mtcli/plugins/media_movel/tests/test_mm.py +13 -13
- mtcli/plugins/media_movel/tests/test_model_media_movel.py +9 -9
- mtcli/plugins/media_movel/view.py +15 -0
- mtcli/plugins/range_medio/__init__.py +5 -2
- mtcli/plugins/range_medio/{command.py → cli.py} +32 -33
- mtcli/plugins/range_medio/conf.py +67 -1
- mtcli/plugins/range_medio/models/average_range_model.py +29 -29
- mtcli/plugins/range_medio/tests/test_rm.py +11 -11
- mtcli/plugins/volume_medio/__init__.py +5 -3
- mtcli/plugins/volume_medio/{command.py → cli.py} +41 -42
- mtcli/plugins/volume_medio/conf.py +67 -1
- mtcli/plugins/volume_medio/models/model_average_volume.py +31 -31
- mtcli/plugins/volume_medio/tests/test_vm.py +21 -21
- mtcli/utils/__init__.py +0 -0
- mtcli/utils/pidfile.py +35 -0
- mtcli/utils/time.py +65 -0
- mtcli/views/base_view.py +80 -0
- mtcli/views/factory_view.py +30 -0
- mtcli/views/full_view.py +40 -69
- mtcli/views/hl_view.py +32 -0
- mtcli/views/range_view.py +47 -0
- mtcli/views/rate_view.py +18 -0
- mtcli/views/volume_view.py +62 -0
- {mtcli-4.0.0.dev0.dist-info → mtcli-4.0.0.dev1.dist-info}/METADATA +14 -9
- mtcli-4.0.0.dev1.dist-info/RECORD +84 -0
- {mtcli-4.0.0.dev0.dist-info → mtcli-4.0.0.dev1.dist-info}/WHEEL +1 -1
- mtcli-4.0.0.dev1.dist-info/entry_points.txt +8 -0
- mtcli-4.0.0.dev1.dist-info/licenses/LICENSE +21 -0
- mtcli/commands/logs.py +0 -42
- mtcli/models/chart_model.py +0 -155
- mtcli/models/conf_model.py +0 -32
- mtcli/models/consecutive_bars_model.py +0 -75
- mtcli/models/signals_model.py +0 -100
- mtcli/models/unconsecutive_bar_model.py +0 -67
- mtcli/mt.py +0 -45
- mtcli/plugins/media_movel/command.py +0 -99
- mtcli/plugins/media_movel/models/__init__.py +0 -1
- mtcli/views/close_view.py +0 -37
- mtcli/views/high_view.py +0 -37
- mtcli/views/low_view.py +0 -37
- mtcli/views/min_view.py +0 -42
- mtcli/views/open_view.py +0 -37
- mtcli/views/ranges_view.py +0 -43
- mtcli/views/rates_view.py +0 -41
- mtcli/views/utils.py +0 -37
- mtcli/views/vars_view.py +0 -46
- mtcli/views/volumes_view.py +0 -55
- mtcli-4.0.0.dev0.dist-info/LICENSE +0 -17
- mtcli-4.0.0.dev0.dist-info/RECORD +0 -64
- mtcli-4.0.0.dev0.dist-info/entry_points.txt +0 -9
- /mtcli/plugins/media_movel/{models/model_media_movel.py → model.py} +0 -0
mtcli/__main__.py
ADDED
mtcli/cli.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI principal do mtcli.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import click
|
|
7
|
+
from .plugin_loader import load_plugins
|
|
8
|
+
from .logger import setup_logger
|
|
9
|
+
|
|
10
|
+
from .commands.bars import bars
|
|
11
|
+
from .commands.doctor import doctor
|
|
12
|
+
|
|
13
|
+
logger = setup_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
|
|
17
|
+
@click.version_option(package_name="mtcli")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
def mt(ctx):
|
|
20
|
+
"""
|
|
21
|
+
CLI principal do mtcli.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if ctx.invoked_subcommand is None:
|
|
25
|
+
click.echo(ctx.get_help())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
mt.add_command(doctor, name="doctor")
|
|
29
|
+
mt.add_command(bars, name="bars")
|
|
30
|
+
|
|
31
|
+
loaded_plugins = load_plugins(mt)
|
|
32
|
+
logger.info("Plugins carregados: %s", loaded_plugins)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@mt.command(name="plugins")
|
|
36
|
+
def list_plugins():
|
|
37
|
+
"""
|
|
38
|
+
Lista os plugins carregados no mtcli.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if not loaded_plugins:
|
|
42
|
+
click.echo("Nenhum plugin carregado.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
click.echo("Plugins carregados:\n")
|
|
46
|
+
for name in loaded_plugins:
|
|
47
|
+
click.echo(f" {name}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
mt()
|
mtcli/commands/bars.py
CHANGED
|
@@ -1,153 +1,75 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@click.
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
@click.option(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
show_default=True,
|
|
77
|
-
help="Timeframe do grafico.",
|
|
78
|
-
)
|
|
79
|
-
@click.option(
|
|
80
|
-
"--count",
|
|
81
|
-
"-c",
|
|
82
|
-
type=int,
|
|
83
|
-
default=PERIODOS,
|
|
84
|
-
show_default=True,
|
|
85
|
-
help="Quantidade de periodos a serem exibidos.",
|
|
86
|
-
)
|
|
87
|
-
@click.option(
|
|
88
|
-
"--date",
|
|
89
|
-
"-d",
|
|
90
|
-
default=DATE,
|
|
91
|
-
show_default=True,
|
|
92
|
-
help="Data para intraday, formato AAAA-MM-DD.",
|
|
93
|
-
)
|
|
94
|
-
@click.option(
|
|
95
|
-
"--numerator",
|
|
96
|
-
"-n",
|
|
97
|
-
is_flag=True,
|
|
98
|
-
default=False,
|
|
99
|
-
show_default=True,
|
|
100
|
-
help="Ativa a numeracao das barras.",
|
|
101
|
-
)
|
|
102
|
-
@click.option(
|
|
103
|
-
"--show-date",
|
|
104
|
-
"-sd",
|
|
105
|
-
is_flag=True,
|
|
106
|
-
default=False,
|
|
107
|
-
show_default=True,
|
|
108
|
-
help="Ativa a datacao das barras.",
|
|
109
|
-
)
|
|
110
|
-
@click.option(
|
|
111
|
-
"--volume",
|
|
112
|
-
"-vo",
|
|
113
|
-
type=click.Choice(["tick", "real"], case_sensitive=False),
|
|
114
|
-
default=VOLUME,
|
|
115
|
-
show_default=True,
|
|
116
|
-
help="Tipo de volume.",
|
|
117
|
-
)
|
|
118
|
-
def bars(symbol, view, period, count, date, numerator, show_date, volume):
|
|
119
|
-
"""Exibe o grafico do MetaTrader 5."""
|
|
120
|
-
period = period.lower()
|
|
121
|
-
view = view.lower()
|
|
122
|
-
rates = RatesModel(symbol, period, count).get_data()
|
|
123
|
-
bars = BarsModel(rates, date).get_bars()
|
|
124
|
-
views = []
|
|
125
|
-
if view in ["m", "ch", "hl"]: # máximas e mínimas
|
|
126
|
-
views = MinView(bars, count, period, date, numerator, show_date).views()
|
|
127
|
-
elif view in ["r", "range"]: # ranges
|
|
128
|
-
views = RangesView(bars, count, period, date, numerator, show_date).views()
|
|
129
|
-
elif view in ["oh", "ohlc"]: # OHLC
|
|
130
|
-
views = RatesView(bars, count, period, date, numerator, show_date).views()
|
|
131
|
-
elif view in ["va", "percentual"]: # variações percentuais
|
|
132
|
-
views = VarsView(bars, count, period, date, numerator, show_date).views()
|
|
133
|
-
elif view in ["o", "open"]: # abertura
|
|
134
|
-
views = OpenView(bars, count, period, date, numerator, show_date).views()
|
|
135
|
-
elif view in ["h", "high"]: # máximas
|
|
136
|
-
views = HighView(bars, count, period, date, numerator, show_date).views()
|
|
137
|
-
elif view in ["l", "low"]: # mínimas
|
|
138
|
-
views = LowView(bars, count, period, date, numerator, show_date).views()
|
|
139
|
-
elif view in ["c", "close"]: # fechamentos
|
|
140
|
-
views = CloseView(bars, count, period, date, numerator, show_date).views()
|
|
141
|
-
elif view in ["v", "volume"]: # volumes
|
|
142
|
-
views = VolumesView(
|
|
143
|
-
bars, count, period, date, numerator, show_date, volume
|
|
144
|
-
).views()
|
|
145
|
-
else: # completo
|
|
146
|
-
views = FullView(bars, count, period, date, numerator, show_date).views()
|
|
147
|
-
if views:
|
|
148
|
-
for view in views:
|
|
149
|
-
click.echo(view)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if __name__ == "__main__":
|
|
153
|
-
bars()
|
|
1
|
+
"""
|
|
2
|
+
Comando CLI `bars`.
|
|
3
|
+
|
|
4
|
+
Responsável por:
|
|
5
|
+
- receber parâmetros via terminal (Click)
|
|
6
|
+
- instanciar a fonte de dados
|
|
7
|
+
- delegar execução ao BarsController
|
|
8
|
+
- imprimir saída no terminal
|
|
9
|
+
|
|
10
|
+
Este comando é a camada de entrada (CLI) no padrão MVC do mtcli.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from ..controllers.bars_controller import BarsController
|
|
16
|
+
from ..conf import (
|
|
17
|
+
DATA_SOURCE_NAME,
|
|
18
|
+
SYMBOL,
|
|
19
|
+
TIMEFRAME,
|
|
20
|
+
MAX_BARS,
|
|
21
|
+
VIEW,
|
|
22
|
+
VOLUME_TYPE,
|
|
23
|
+
)
|
|
24
|
+
from ..data.factory import create_data_source
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click.option("--symbol", "-s", default=SYMBOL, show_default=True, help="Código do ativo (ex: WINM26)")
|
|
29
|
+
@click.option("--timeframe", "-t", "period", default=TIMEFRAME, show_default=True, help="Timeframe (ex: M1, M5, D1)")
|
|
30
|
+
@click.option("--max-bars", "-mb", "count", default=MAX_BARS, show_default=True, help="Quantidade de barras")
|
|
31
|
+
@click.option("--view", "-v", default=VIEW, show_default=True, help="Formato da view (hl_view, full_view, etc)")
|
|
32
|
+
@click.option("--date", "-d", default=None, show_default=True, help="Filtrar pregão (YYYY-MM-DD)")
|
|
33
|
+
@click.option("--numerator", "-n", is_flag=True, show_default=True, help="Numerar barras")
|
|
34
|
+
@click.option("--show-date", "-sd", is_flag=True, show_default=True, help="Exibir data/hora")
|
|
35
|
+
@click.option("--volume-type", "-vt", "volume", default=VOLUME_TYPE, show_default=True, help="Tipo de volume (tick/real)")
|
|
36
|
+
@click.option("--data-source", "-ds", "source", default=None, show_default=True, help="Fonte de dados (mt5/csv)")
|
|
37
|
+
def bars(symbol, period, count, view, date, numerator, show_date, volume, source):
|
|
38
|
+
"""
|
|
39
|
+
Executa o comando `bars`.
|
|
40
|
+
|
|
41
|
+
Fluxo:
|
|
42
|
+
1. Resolve fonte de dados
|
|
43
|
+
2. Executa controller
|
|
44
|
+
3. Renderiza saída linha a linha
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
symbol (str): Ativo
|
|
48
|
+
period (str): Timeframe
|
|
49
|
+
count (int): Quantidade de barras
|
|
50
|
+
view (str): Nome da view
|
|
51
|
+
date (str | None): Filtro de pregão
|
|
52
|
+
numerator (bool): Numerar barras
|
|
53
|
+
show_date (bool): Mostrar timestamp
|
|
54
|
+
volume (str | None): Tipo de volume
|
|
55
|
+
source (str | None): Fonte de dados
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
source_name = source or DATA_SOURCE_NAME
|
|
59
|
+
data_source = create_data_source(source_name)
|
|
60
|
+
|
|
61
|
+
controller = BarsController(data_source)
|
|
62
|
+
|
|
63
|
+
lines = controller.execute(
|
|
64
|
+
symbol=symbol,
|
|
65
|
+
period=period,
|
|
66
|
+
count=count,
|
|
67
|
+
date=date,
|
|
68
|
+
view=view,
|
|
69
|
+
numerator=numerator,
|
|
70
|
+
show_date=show_date,
|
|
71
|
+
volume=volume,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
for line in lines:
|
|
75
|
+
click.echo(line)
|
mtcli/commands/conf.py
CHANGED
|
@@ -1,50 +1,44 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Comando para exibir configurações disponíveis.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
import click
|
|
4
6
|
|
|
5
|
-
from mtcli.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@click.command(
|
|
9
|
-
"conf",
|
|
10
|
-
help="Gerencia as configurações salvas no arquivo mtcli.ini (adicionar, editar, listar).",
|
|
11
|
-
)
|
|
12
|
-
@click.option("--list", "list_", is_flag=True, help="Lista todas as configurações.")
|
|
13
|
-
@click.option("--set", "set_", nargs=2, help="Define o valor de uma configuração.")
|
|
14
|
-
@click.option("--get", help="Exibe o valor de uma configuração.")
|
|
15
|
-
@click.option("--reset", is_flag=True, help="Redefine as configurações padrão.")
|
|
16
|
-
def conf(list_, set_, get, reset):
|
|
17
|
-
"""Gerencia configurações registradas no mtcli.ini."""
|
|
18
|
-
conf = ConfModel("mtcli.ini")
|
|
19
|
-
config = conf.carregar()
|
|
20
|
-
|
|
21
|
-
if list_:
|
|
22
|
-
for key in config["DEFAULT"]:
|
|
23
|
-
click.echo(f"{key} = {config['DEFAULT'][key]}")
|
|
24
|
-
|
|
25
|
-
elif set_:
|
|
26
|
-
chave, valor = set_
|
|
27
|
-
config["DEFAULT"][chave] = valor
|
|
28
|
-
conf.salvar(config)
|
|
29
|
-
click.echo(f"Configuração '{chave}' definida como '{valor}'.")
|
|
30
|
-
|
|
31
|
-
elif get:
|
|
32
|
-
valor = config["DEFAULT"].get(get)
|
|
33
|
-
if valor is not None:
|
|
34
|
-
click.echo(f"{get} = {valor}")
|
|
35
|
-
else:
|
|
36
|
-
click.echo(f"Configuração '{get}' não encontrada.")
|
|
37
|
-
|
|
38
|
-
elif reset:
|
|
39
|
-
config["DEFAULT"].clear()
|
|
40
|
-
conf.salvar(config)
|
|
41
|
-
click.echo("Configurações redefinidas.")
|
|
7
|
+
from mtcli.config_registry import registry
|
|
8
|
+
from mtcli.conf import conf
|
|
42
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)
|
|
43
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
|
+
|
|
44
36
|
click.echo(
|
|
45
|
-
"
|
|
37
|
+
f"{opt.section}.{opt.name} = {value} "
|
|
38
|
+
f"(default={opt.default})"
|
|
46
39
|
)
|
|
47
40
|
|
|
41
|
+
if opt.description:
|
|
42
|
+
click.echo(f" {opt.description}")
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
conf()
|
|
44
|
+
click.echo()
|
mtcli/commands/doctor.py
ADDED
|
@@ -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.")
|