mtcli 3.0.0.dev2__tar.gz → 3.2.0.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/PKG-INFO +1 -1
  2. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/commands/conf.py +50 -50
  3. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/conf.py +101 -101
  4. mtcli-3.2.0.dev0/mtcli/logger.py +45 -0
  5. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/conf_model.py +33 -33
  6. mtcli-3.2.0.dev0/mtcli/mt5_context.py +33 -0
  7. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/pyproject.toml +1 -1
  8. mtcli-3.0.0.dev2/mtcli/logger.py +0 -29
  9. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/LICENSE +0 -0
  10. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/README.md +0 -0
  11. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/__init__.py +0 -0
  12. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/commands/__init__.py +0 -0
  13. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/commands/bars.py +0 -0
  14. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/commands/logs.py +0 -0
  15. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/conecta.py +0 -0
  16. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/data/__init__.py +0 -0
  17. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/data/base.py +0 -0
  18. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/data/csv.py +0 -0
  19. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/data/mt5.py +0 -0
  20. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/__init__.py +0 -0
  21. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/bar_model.py +0 -0
  22. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/bars_model.py +0 -0
  23. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/chart_model.py +0 -0
  24. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/consecutive_bars_model.py +0 -0
  25. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/rates_model.py +0 -0
  26. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/signals_model.py +0 -0
  27. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/models/unconsecutive_bar_model.py +0 -0
  28. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/mt.py +0 -0
  29. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugin.py +0 -0
  30. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/__init__.py +0 -0
  31. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/__init__.py +0 -0
  32. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/command.py +0 -0
  33. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/conf.py +0 -0
  34. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/models/__init__.py +0 -0
  35. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/models/model_agressao.py +0 -0
  36. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/views/__init__.py +0 -0
  37. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/agressao/views/view_agressao.py +0 -0
  38. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/__init__.py +0 -0
  39. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/command.py +0 -0
  40. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/conf.py +0 -0
  41. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  42. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  43. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  44. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  45. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  46. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/__init__.py +0 -0
  47. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/command.py +0 -0
  48. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/conf.py +0 -0
  49. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  50. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  51. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  52. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  53. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/__init__.py +0 -0
  54. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/command.py +0 -0
  55. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/conf.py +0 -0
  56. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  57. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  58. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  59. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  60. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/__init__.py +0 -0
  61. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/close_view.py +0 -0
  62. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/full_view.py +0 -0
  63. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/high_view.py +0 -0
  64. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/low_view.py +0 -0
  65. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/min_view.py +0 -0
  66. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/open_view.py +0 -0
  67. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/ranges_view.py +0 -0
  68. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/rates_view.py +0 -0
  69. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/vars_view.py +0 -0
  70. {mtcli-3.0.0.dev2 → mtcli-3.2.0.dev0}/mtcli/views/volumes_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mtcli
3
- Version: 3.0.0.dev2
3
+ Version: 3.2.0.dev0
4
4
  Summary: Aplicativo CLI para exibir gráficos do MetaTrader 5 em texto acessível ao leitor de telas
5
5
  License: GPL-3.0
6
6
  Keywords: MetaTrader 5,trading,CLI
@@ -1,50 +1,50 @@
1
- """Gerencia configurações registradas no mtcli.ini."""
2
-
3
- import os
4
- import click
5
- from mtcli.models.conf_model import ConfModel
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.")
42
-
43
- else:
44
- click.echo(
45
- "Nenhuma opção fornecida. Use --help para ver as opções disponíveis."
46
- )
47
-
48
-
49
- if __name__ == "__main__":
50
- conf()
1
+ """Gerencia configurações registradas no mtcli.ini."""
2
+
3
+ import os
4
+ import click
5
+ from mtcli.models.conf_model import ConfModel
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.")
42
+
43
+ else:
44
+ click.echo(
45
+ "Nenhuma opção fornecida. Use --help para ver as opções disponíveis."
46
+ )
47
+
48
+
49
+ if __name__ == "__main__":
50
+ conf()
@@ -1,101 +1,101 @@
1
- import os
2
- import MetaTrader5 as mt5
3
- from mtcli.conecta import conectar, shutdown
4
- from mtcli.models.conf_model import ConfModel
5
-
6
-
7
- config = ConfModel("mtcli.ini").carregar()
8
-
9
- section = "DEFAULT"
10
- symbol = os.getenv("SYMBOL", config[section].get("symbol", fallback="WIN$N"))
11
- digitos = int(os.getenv("DIGITOS", config[section].getint("digitos", fallback=2)))
12
- period = os.getenv("PERIOD", config[section].get("period", fallback="D1"))
13
- periodos = count = int(
14
- os.getenv("COUNT", config[section].getint("count", fallback=999))
15
- )
16
- view = os.getenv("VIEW", config[section].get("view", fallback="ch"))
17
- volume = os.getenv("VOLUME", config[section].get("volume", fallback="tick"))
18
- date = os.getenv("DATE", config[section].get("date", fallback=""))
19
-
20
- lateral = os.getenv("LATERAL", config[section].get("lateral", fallback="doji"))
21
- alta = os.getenv("ALTA", config[section].get("alta", fallback="verde"))
22
- baixa = os.getenv("BAIXA", config[section].get("baixa", fallback="vermelho"))
23
- rompimento_alta = os.getenv(
24
- "ROMPIMENTO_ALTA", config[section].get("rompimento_alta", fallback="c")
25
- )
26
- rompimento_baixa = os.getenv(
27
- "ROMPIMENTO_BAIXA", config[section].get("rompimento_baixa", fallback="v")
28
- )
29
- percentual_rompimento = int(
30
- os.getenv(
31
- "PERCENTUAL_ROMPIMENTO",
32
- config[section].getint("percentual_rompimento", fallback=50),
33
- )
34
- )
35
- percentual_doji = int(
36
- os.getenv("PERCENTUAL_DOJI", config[section].getint("percentual_doji", fallback=10))
37
- )
38
- up_bar = os.getenv("UP_BAR", config[section].get("up_bar", fallback="asc"))
39
- down_bar = os.getenv("DOWN_BAR", config[section].get("down_bar", fallback="desc"))
40
- inside_bar = os.getenv("INSIDE_BAR", config[section].get("inside_bar", fallback="ib"))
41
- outside_bar = os.getenv(
42
- "OUTSIDE_BAR", config[section].get("outside_bar", fallback="ob")
43
- )
44
- sombra_superior = os.getenv(
45
- "SOMBRA_SUPERIOR", config[section].get("sombra_superior", fallback="top")
46
- )
47
- sombra_inferior = os.getenv(
48
- "SOMBRA_INFERIOR", config[section].get("sombra_inferior", fallback="bottom")
49
- )
50
- data_source = dados = os.getenv("DADOS", config[section].get("dados", fallback="mt5"))
51
- csv_path = mt5_pasta = os.getenv(
52
- "MT5_PASTA", config[section].get("mt5_pasta", fallback="")
53
- )
54
-
55
-
56
- def get_data_source():
57
- from mtcli.data import CsvDataSource, MT5DataSource
58
-
59
- if data_source.lower() == "csv":
60
- return CsvDataSource()
61
- elif data_source.lower() == "mt5":
62
- return MT5DataSource()
63
- else:
64
- raise ValueError(f"Fonte de dados desconhecida: {data_source}")
65
-
66
-
67
- if not csv_path:
68
- conectar()
69
- terminal_info = mt5.terminal_info()
70
- if terminal_info is None:
71
- raise RuntimeError("Não foi possível obter as informações do terminal.")
72
-
73
- csv_path = terminal_info.data_path + "/MQL5/Files"
74
- shutdown()
75
-
76
- csv_path = csv_path.replace("\\", "/")
77
- csv_path += "/"
78
-
79
- timeframes = [
80
- "mn1",
81
- "w1",
82
- "d1",
83
- "h12",
84
- "h8",
85
- "h6",
86
- "h4",
87
- "h3",
88
- "h2",
89
- "h1",
90
- "m30",
91
- "m20",
92
- "m15",
93
- "m12",
94
- "m10",
95
- "m6",
96
- "m5",
97
- "m4",
98
- "m3",
99
- "m2",
100
- "m1",
101
- ]
1
+ import os
2
+ import MetaTrader5 as mt5
3
+ from mtcli.conecta import conectar, shutdown
4
+ from mtcli.models.conf_model import ConfModel
5
+
6
+
7
+ config = ConfModel("mtcli.ini").carregar()
8
+
9
+ section = "DEFAULT"
10
+ symbol = os.getenv("SYMBOL", config[section].get("symbol", fallback="WIN$N"))
11
+ digitos = int(os.getenv("DIGITOS", config[section].getint("digitos", fallback=2)))
12
+ period = os.getenv("PERIOD", config[section].get("period", fallback="D1"))
13
+ periodos = count = int(
14
+ os.getenv("COUNT", config[section].getint("count", fallback=999))
15
+ )
16
+ view = os.getenv("VIEW", config[section].get("view", fallback="ch"))
17
+ volume = os.getenv("VOLUME", config[section].get("volume", fallback="tick"))
18
+ date = os.getenv("DATE", config[section].get("date", fallback=""))
19
+
20
+ lateral = os.getenv("LATERAL", config[section].get("lateral", fallback="doji"))
21
+ alta = os.getenv("ALTA", config[section].get("alta", fallback="verde"))
22
+ baixa = os.getenv("BAIXA", config[section].get("baixa", fallback="vermelho"))
23
+ rompimento_alta = os.getenv(
24
+ "ROMPIMENTO_ALTA", config[section].get("rompimento_alta", fallback="c")
25
+ )
26
+ rompimento_baixa = os.getenv(
27
+ "ROMPIMENTO_BAIXA", config[section].get("rompimento_baixa", fallback="v")
28
+ )
29
+ percentual_rompimento = int(
30
+ os.getenv(
31
+ "PERCENTUAL_ROMPIMENTO",
32
+ config[section].getint("percentual_rompimento", fallback=50),
33
+ )
34
+ )
35
+ percentual_doji = int(
36
+ os.getenv("PERCENTUAL_DOJI", config[section].getint("percentual_doji", fallback=10))
37
+ )
38
+ up_bar = os.getenv("UP_BAR", config[section].get("up_bar", fallback="asc"))
39
+ down_bar = os.getenv("DOWN_BAR", config[section].get("down_bar", fallback="desc"))
40
+ inside_bar = os.getenv("INSIDE_BAR", config[section].get("inside_bar", fallback="ib"))
41
+ outside_bar = os.getenv(
42
+ "OUTSIDE_BAR", config[section].get("outside_bar", fallback="ob")
43
+ )
44
+ sombra_superior = os.getenv(
45
+ "SOMBRA_SUPERIOR", config[section].get("sombra_superior", fallback="top")
46
+ )
47
+ sombra_inferior = os.getenv(
48
+ "SOMBRA_INFERIOR", config[section].get("sombra_inferior", fallback="bottom")
49
+ )
50
+ data_source = dados = os.getenv("DADOS", config[section].get("dados", fallback="mt5"))
51
+ csv_path = mt5_pasta = os.getenv(
52
+ "MT5_PASTA", config[section].get("mt5_pasta", fallback="")
53
+ )
54
+
55
+
56
+ def get_data_source():
57
+ from mtcli.data import CsvDataSource, MT5DataSource
58
+
59
+ if data_source.lower() == "csv":
60
+ return CsvDataSource()
61
+ elif data_source.lower() == "mt5":
62
+ return MT5DataSource()
63
+ else:
64
+ raise ValueError(f"Fonte de dados desconhecida: {data_source}")
65
+
66
+
67
+ if not csv_path:
68
+ conectar()
69
+ terminal_info = mt5.terminal_info()
70
+ if terminal_info is None:
71
+ raise RuntimeError("Não foi possível obter as informações do terminal.")
72
+
73
+ csv_path = terminal_info.data_path + "/MQL5/Files"
74
+ shutdown()
75
+
76
+ csv_path = csv_path.replace("\\", "/")
77
+ csv_path += "/"
78
+
79
+ timeframes = [
80
+ "mn1",
81
+ "w1",
82
+ "d1",
83
+ "h12",
84
+ "h8",
85
+ "h6",
86
+ "h4",
87
+ "h3",
88
+ "h2",
89
+ "h1",
90
+ "m30",
91
+ "m20",
92
+ "m15",
93
+ "m12",
94
+ "m10",
95
+ "m6",
96
+ "m5",
97
+ "m4",
98
+ "m3",
99
+ "m2",
100
+ "m1",
101
+ ]
@@ -0,0 +1,45 @@
1
+ import logging
2
+ from logging.handlers import RotatingFileHandler
3
+ import os
4
+
5
+ LOG_DIR = os.path.join(os.path.expanduser("~"), ".mtcli")
6
+ os.makedirs(LOG_DIR, exist_ok=True)
7
+ LOG_FILE = os.path.join(LOG_DIR, "mtcli.log")
8
+
9
+
10
+ def setup_logger(name: str = "mtcli") -> logging.Logger:
11
+ """Configura logger rotativo com saída em arquivo e console.
12
+
13
+ - Escreve logs em ~/.mtcli/mtcli.log (máx. 2 MB, 3 backups).
14
+ - Mostra logs também no console (stdout), capturáveis via pytest/caplog.
15
+ - Evita duplicar handlers.
16
+ """
17
+
18
+ logger = logging.getLogger(name)
19
+ logger.setLevel(logging.DEBUG)
20
+
21
+ formatter = logging.Formatter(
22
+ "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
23
+ datefmt="%Y-%m-%d %H:%M:%S",
24
+ )
25
+
26
+ # === File handler rotativo ===
27
+ if not any(isinstance(h, RotatingFileHandler) for h in logger.handlers):
28
+ file_handler = RotatingFileHandler(LOG_FILE, maxBytes=2_000_000, backupCount=3)
29
+ file_handler.setFormatter(formatter)
30
+ logger.addHandler(file_handler)
31
+
32
+ # === Stream handler (console) ===
33
+ if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
34
+ console_handler = logging.StreamHandler()
35
+ console_handler.setFormatter(formatter)
36
+ logger.addHandler(console_handler)
37
+
38
+ # Permite que pytest caplog capture logs
39
+ logger.propagate = True
40
+
41
+ return logger
42
+
43
+
44
+ # Inicializa logger padrão
45
+ log = setup_logger()
@@ -1,33 +1,33 @@
1
- """Modelo de configurações."""
2
-
3
- import os
4
- import configparser
5
- from configparser import MissingSectionHeaderError
6
-
7
-
8
- class ConfigFormatError(Exception):
9
- pass
10
-
11
-
12
- class ConfModel:
13
-
14
- def __init__(self, configuracoes):
15
- self.CONFIG_PATH = os.path.abspath(configuracoes)
16
-
17
- def carregar(self):
18
- config = configparser.ConfigParser()
19
- if os.path.exists(self.CONFIG_PATH):
20
- try:
21
- config.read(self.CONFIG_PATH)
22
- except MissingSectionHeaderError:
23
- raise ConfigFormatError(
24
- f"Erro: o arquivo '{self.CONFIG_PATH}' não contém seções válidas.\n"
25
- "Certifique-se de que ele está no formato correto:\n[padrao]\nCHAVE=valor"
26
- )
27
- else:
28
- config["DEFAULT"] = {}
29
- return config
30
-
31
- def salvar(self, config):
32
- with open(self.CONFIG_PATH, "w") as f:
33
- config.write(f)
1
+ """Modelo de configurações."""
2
+
3
+ import os
4
+ import configparser
5
+ from configparser import MissingSectionHeaderError
6
+
7
+
8
+ class ConfigFormatError(Exception):
9
+ pass
10
+
11
+
12
+ class ConfModel:
13
+
14
+ def __init__(self, configuracoes):
15
+ self.CONFIG_PATH = os.path.abspath(configuracoes)
16
+
17
+ def carregar(self):
18
+ config = configparser.ConfigParser()
19
+ if os.path.exists(self.CONFIG_PATH):
20
+ try:
21
+ config.read(self.CONFIG_PATH)
22
+ except MissingSectionHeaderError:
23
+ raise ConfigFormatError(
24
+ f"Erro: o arquivo '{self.CONFIG_PATH}' não contém seções válidas.\n"
25
+ "Certifique-se de que ele está no formato correto:\n[padrao]\nCHAVE=valor"
26
+ )
27
+ else:
28
+ config["DEFAULT"] = {}
29
+ return config
30
+
31
+ def salvar(self, config):
32
+ with open(self.CONFIG_PATH, "w") as f:
33
+ config.write(f)
@@ -0,0 +1,33 @@
1
+ """
2
+ Gerencia o ciclo de conexão com o MetaTrader 5.
3
+ Fornece o contexto 'mt5_conexao' para uso seguro em blocos with.
4
+ """
5
+
6
+ from contextlib import contextmanager
7
+ from mtcli.logger import setup_logger
8
+ from mtcli.conecta import conectar, shutdown
9
+
10
+ log = setup_logger()
11
+
12
+
13
+ @contextmanager
14
+ def mt5_conexao():
15
+ """
16
+ Context manager para conexão com o MetaTrader 5.
17
+ - Chama `conectar()` ao entrar no contexto.
18
+ - Chama `shutdown()` ao sair.
19
+ - Loga falhas e garante fechamento seguro.
20
+ """
21
+ try:
22
+ log.debug("Inicializando conexão com MetaTrader 5...")
23
+ conectar()
24
+ yield
25
+ except Exception as e:
26
+ log.error(f"Erro ao conectar ao MetaTrader 5: {e}")
27
+ raise
28
+ finally:
29
+ try:
30
+ shutdown()
31
+ log.debug("Conexão com MetaTrader 5 encerrada.")
32
+ except Exception as e:
33
+ log.error(f"Erro ao encerrar conexão MT5: {e}")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.0.0.dev2"
3
+ version = "3.2.0-dev0"
4
4
  description = "Aplicativo CLI para exibir gráficos do MetaTrader 5 em texto acessível ao leitor de telas"
5
5
  authors = [
6
6
  {name = "Valmir França da Silva",email = "vfranca3@gmail.com"}
@@ -1,29 +0,0 @@
1
- """Módulo de logging."""
2
-
3
- import os
4
- import logging
5
- from logging.handlers import RotatingFileHandler
6
-
7
-
8
- def setup_logger(name="mtcli"):
9
- if os.name == "nt":
10
- base_dir = os.getenv("APPDATA", os.path.expanduser("~"))
11
- log_dir = os.path.join(base_dir, name, "logs")
12
- else:
13
- base_dir = os.path.expanduser("~/.local/share")
14
- log_dir = os.path.join(base_dir, name, "logs")
15
-
16
- os.makedirs(log_dir, exist_ok=True)
17
- log_path = os.path.join(log_dir, f"{name}.log")
18
-
19
- handler = RotatingFileHandler(log_path, maxBytes=1_000_000, backupCount=3)
20
- formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
21
- handler.setFormatter(formatter)
22
-
23
- logger = logging.getLogger(name)
24
- logger.setLevel(logging.DEBUG)
25
- if not logger.handlers:
26
- logger.addHandler(handler)
27
- logger.propagate = False
28
-
29
- return logger
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes