mtcli 3.7.1__tar.gz → 3.7.2.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 (74) hide show
  1. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/PKG-INFO +1 -1
  2. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/conf.py +325 -309
  3. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/logger.py +178 -136
  4. mtcli-3.7.2.dev0/mtcli/utils/__init__.py +0 -0
  5. mtcli-3.7.2.dev0/mtcli/utils/pidfile.py +35 -0
  6. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/pyproject.toml +1 -1
  7. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/LICENSE +0 -0
  8. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/README.md +0 -0
  9. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/__init__.py +0 -0
  10. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/cli.py +0 -0
  11. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/__init__.py +0 -0
  12. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/bars.py +0 -0
  13. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/conf.py +0 -0
  14. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/doctor.py +0 -0
  15. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/conecta.py +0 -0
  16. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/config_registre.py +0 -0
  17. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/__init__.py +0 -0
  18. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/base.py +0 -0
  19. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/csv.py +0 -0
  20. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/mt5.py +0 -0
  21. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/database.py +0 -0
  22. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/domain/__init__.py +0 -0
  23. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/domain/timeframe.py +0 -0
  24. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/__init__.py +0 -0
  25. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/tick_cache.py +0 -0
  26. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/tick_repository.py +0 -0
  27. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/__init__.py +0 -0
  28. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/bar_model.py +0 -0
  29. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/bars_model.py +0 -0
  30. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/chart_model.py +0 -0
  31. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/conf_model.py +0 -0
  32. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/consecutive_bars_model.py +0 -0
  33. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/rates_model.py +0 -0
  34. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/signals_model.py +0 -0
  35. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/unconsecutive_bar_model.py +0 -0
  36. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/mt5_context.py +0 -0
  37. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin.py +0 -0
  38. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin_loader.py +0 -0
  39. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin_manager.py +0 -0
  40. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/__init__.py +0 -0
  41. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/exemplo.py-dist +0 -0
  42. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/__init__.py +0 -0
  43. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/cli.py +0 -0
  44. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/conf.py +0 -0
  45. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  46. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  47. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  48. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  49. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  50. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/__init__.py +0 -0
  51. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/cli.py +0 -0
  52. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/conf.py +0 -0
  53. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  54. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  55. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  56. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  57. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/__init__.py +0 -0
  58. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/cli.py +0 -0
  59. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/conf.py +0 -0
  60. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  61. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  62. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  63. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  64. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/__init__.py +0 -0
  65. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/close_view.py +0 -0
  66. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/full_view.py +0 -0
  67. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/high_view.py +0 -0
  68. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/low_view.py +0 -0
  69. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/min_view.py +0 -0
  70. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/open_view.py +0 -0
  71. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/ranges_view.py +0 -0
  72. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/rates_view.py +0 -0
  73. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/vars_view.py +0 -0
  74. {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/volumes_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli
3
- Version: 3.7.1
3
+ Version: 3.7.2.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
@@ -1,309 +1,325 @@
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()
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()
310
+
311
+ # ---------------------------------------------------------
312
+ # controle de processo em execução
313
+ # ---------------------------------------------------------
314
+
315
+ RUN_DIR = os.path.join(
316
+ os.getenv("APPDATA", os.path.expanduser("~")),
317
+ "mtcli",
318
+ "run"
319
+ )
320
+
321
+ os.makedirs(RUN_DIR, exist_ok=True)
322
+
323
+ PID_FILE = os.path.join(RUN_DIR, "risco.pid")
324
+ STOP_FILE = os.path.join(RUN_DIR, "risco.stop")
325
+ HEARTBEAT_FILE = os.path.join(RUN_DIR, "risco.heartbeat")
@@ -1,136 +1,178 @@
1
- """
2
- Sistema central de logging do mtcli.
3
-
4
- Este módulo fornece uma função única `setup_logger()` utilizada por todo
5
- o ecossistema de plugins do mtcli para configurar logging consistente.
6
-
7
- Características principais
8
- --------------------------
9
-
10
- Arquivo de log rotativo em:
11
- %APPDATA%/mtcli/logs/mtcli.log
12
-
13
- Rotação automática:
14
- - tamanho máximo: 2 MB
15
- - até 3 arquivos de backup
16
-
17
- Proteção contra duplicação de handlers quando plugins
18
- inicializam o logger múltiplas vezes.
19
-
20
- Encoding UTF-8 garantido (evita problemas de acentuação
21
- no Windows).
22
-
23
- Compatível com pytest (caplog).
24
-
25
- Observação
26
- ----------
27
-
28
- Os logs **não são exibidos no console**.
29
- Toda saída é direcionada exclusivamente para o arquivo de log.
30
- """
31
-
32
- import logging
33
- from logging.handlers import RotatingFileHandler
34
- import os
35
-
36
-
37
- # ==========================================================
38
- # DIRETÓRIO DE LOG
39
- # ==========================================================
40
-
41
- base_dir = os.getenv("APPDATA", os.path.expanduser("~"))
42
-
43
- LOG_DIR = os.path.join(base_dir, "mtcli", "logs")
44
-
45
- os.makedirs(LOG_DIR, exist_ok=True)
46
-
47
- LOG_FILE = os.path.join(LOG_DIR, "mtcli.log")
48
-
49
-
50
- # ==========================================================
51
- # LOGGER SETUP
52
- # ==========================================================
53
-
54
- def setup_logger(name: str = "mtcli") -> logging.Logger:
55
- """
56
- Cria ou retorna um logger configurado para o mtcli.
57
-
58
- O logger utiliza **apenas um handler de arquivo rotativo**.
59
- Nenhuma saída é enviada ao console.
60
-
61
- A função é **idempotente**, ou seja, pode ser chamada
62
- múltiplas vezes sem duplicar handlers.
63
-
64
- Parameters
65
- ----------
66
- name : str
67
- Nome do logger (normalmente `__name__`).
68
-
69
- Returns
70
- -------
71
- logging.Logger
72
- Instância configurada do logger.
73
- """
74
-
75
- logger = logging.getLogger(name)
76
-
77
- logger.setLevel(logging.DEBUG)
78
-
79
- formatter = logging.Formatter(
80
- "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
81
- datefmt="%Y-%m-%d %H:%M:%S",
82
- )
83
-
84
- # ======================================================
85
- # REMOVE STREAM HANDLERS (garante silêncio no console)
86
- # ======================================================
87
-
88
- for handler in list(logger.handlers):
89
- if isinstance(handler, logging.StreamHandler) and not isinstance(handler, RotatingFileHandler):
90
- logger.removeHandler(handler)
91
-
92
- # ======================================================
93
- # FILE HANDLER ROTATIVO
94
- # ======================================================
95
-
96
- file_handler_exists = any(
97
- isinstance(h, RotatingFileHandler) for h in logger.handlers
98
- )
99
-
100
- if not file_handler_exists:
101
-
102
- file_handler = RotatingFileHandler(
103
- LOG_FILE,
104
- maxBytes=2_000_000,
105
- backupCount=3,
106
- encoding="utf-8",
107
- delay=True,
108
- )
109
-
110
- file_handler.setFormatter(formatter)
111
-
112
- logger.addHandler(file_handler)
113
-
114
- # ======================================================
115
- # PROPAGATION
116
- # ======================================================
117
-
118
- # Permite que pytest caplog capture logs
119
- logger.propagate = True
120
-
121
- return logger
122
-
123
-
124
- # ==========================================================
125
- # LOGGER PADRÃO DO MTCLI
126
- # ==========================================================
127
-
128
- """
129
- Logger padrão utilizado por módulos internos do mtcli.
130
-
131
- Plugins geralmente criam seu próprio logger usando:
132
-
133
- setup_logger(__name__)
134
- """
135
-
136
- log = setup_logger()
1
+ """
2
+ Sistema central de logging do mtcli.
3
+
4
+ Este módulo fornece uma função única `setup_logger()` utilizada por todo
5
+ o ecossistema de plugins do mtcli para configurar logging consistente.
6
+
7
+ Características principais
8
+ --------------------------
9
+
10
+ * Arquivo de log rotativo em:
11
+ %APPDATA%/mtcli/logs/
12
+
13
+ * Rotação automática:
14
+ - tamanho máximo: 2 MB
15
+ - até 3 arquivos de backup
16
+
17
+ * Proteção contra duplicação de handlers quando plugins
18
+ inicializam o logger múltiplas vezes.
19
+
20
+ * Encoding UTF-8 garantido (evita problemas de acentuação
21
+ no Windows).
22
+
23
+ * Compatível com pytest (caplog).
24
+
25
+ * Suporte opcional a logs por processo (multi-process safe)
26
+
27
+ Variáveis de ambiente
28
+ ---------------------
29
+
30
+ MTCLI_LOG_PER_PROCESS=1
31
+ cria arquivos separados por PID
32
+ exemplo: mtcli-1234.log
33
+
34
+ MTCLI_LOG_NAME=risco
35
+ define nome base do arquivo de log
36
+
37
+ Observação
38
+ ----------
39
+
40
+ Os logs **não são exibidos no console**.
41
+ Toda saída é direcionada exclusivamente para arquivo.
42
+ """
43
+
44
+ import logging
45
+ from logging.handlers import RotatingFileHandler
46
+ import os
47
+ from pathlib import Path
48
+
49
+
50
+ # ==========================================================
51
+ # DIRETÓRIO DE LOG
52
+ # ==========================================================
53
+
54
+ base_dir = os.getenv("APPDATA", os.path.expanduser("~"))
55
+
56
+ LOG_DIR = Path(base_dir) / "mtcli" / "logs"
57
+
58
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
59
+
60
+
61
+ # ==========================================================
62
+ # RESOLUÇÃO DO NOME DO LOG
63
+ # ==========================================================
64
+
65
+ def _resolve_log_file() -> Path:
66
+ """
67
+ Resolve dinamicamente o caminho do arquivo de log.
68
+
69
+ Mantém compatibilidade com versões anteriores
70
+ mas permite novos modos via variáveis de ambiente.
71
+ """
72
+
73
+ log_name = os.getenv("MTCLI_LOG_NAME", "mtcli")
74
+
75
+ per_process = os.getenv("MTCLI_LOG_PER_PROCESS")
76
+
77
+ if per_process:
78
+ pid = os.getpid()
79
+ filename = f"{log_name}-{pid}.log"
80
+ else:
81
+ filename = f"{log_name}.log"
82
+
83
+ return LOG_DIR / filename
84
+
85
+
86
+ LOG_FILE = _resolve_log_file()
87
+
88
+
89
+ # ==========================================================
90
+ # LOGGER SETUP
91
+ # ==========================================================
92
+
93
+ def setup_logger(name: str = "mtcli") -> logging.Logger:
94
+ """
95
+ Cria ou retorna um logger configurado para o mtcli.
96
+
97
+ O logger utiliza **apenas um handler de arquivo rotativo**.
98
+ Nenhuma saída é enviada ao console.
99
+
100
+ A função é **idempotente**, ou seja, pode ser chamada
101
+ múltiplas vezes sem duplicar handlers.
102
+
103
+ Parameters
104
+ ----------
105
+ name : str
106
+ Nome do logger (normalmente `__name__`).
107
+
108
+ Returns
109
+ -------
110
+ logging.Logger
111
+ Instância configurada do logger.
112
+ """
113
+
114
+ logger = logging.getLogger(name)
115
+
116
+ logger.setLevel(logging.DEBUG)
117
+
118
+ formatter = logging.Formatter(
119
+ "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
120
+ datefmt="%Y-%m-%d %H:%M:%S",
121
+ )
122
+
123
+ # ======================================================
124
+ # REMOVE STREAM HANDLERS (silencia console)
125
+ # ======================================================
126
+
127
+ for handler in list(logger.handlers):
128
+
129
+ if isinstance(handler, logging.StreamHandler) and not isinstance(
130
+ handler, RotatingFileHandler
131
+ ):
132
+ logger.removeHandler(handler)
133
+
134
+ # ======================================================
135
+ # EVITA DUPLICAÇÃO DE FILE HANDLER
136
+ # ======================================================
137
+
138
+ for handler in logger.handlers:
139
+
140
+ if isinstance(handler, RotatingFileHandler):
141
+
142
+ try:
143
+ if Path(handler.baseFilename) == LOG_FILE:
144
+ return logger
145
+ except Exception:
146
+ pass
147
+
148
+ # ======================================================
149
+ # FILE HANDLER ROTATIVO
150
+ # ======================================================
151
+
152
+ file_handler = RotatingFileHandler(
153
+ LOG_FILE,
154
+ maxBytes=2_000_000,
155
+ backupCount=3,
156
+ encoding="utf-8",
157
+ delay=True,
158
+ )
159
+
160
+ file_handler.setFormatter(formatter)
161
+
162
+ logger.addHandler(file_handler)
163
+
164
+ # ======================================================
165
+ # PROPAGATION
166
+ # ======================================================
167
+
168
+ # Permite que pytest caplog capture logs
169
+ logger.propagate = True
170
+
171
+ return logger
172
+
173
+
174
+ # ==========================================================
175
+ # LOGGER PADRÃO DO MTCLI
176
+ # ==========================================================
177
+
178
+ log = setup_logger()
File without changes
@@ -0,0 +1,35 @@
1
+ import os
2
+ import sys
3
+ import signal
4
+
5
+
6
+ class PidFile:
7
+
8
+ def __init__(self, path: str):
9
+ self.path = path
10
+
11
+ def create(self):
12
+
13
+ if os.path.exists(self.path):
14
+
15
+ with open(self.path) as f:
16
+ pid = int(f.read().strip())
17
+
18
+ if self._pid_running(pid):
19
+ print(f"Monitor já está rodando (PID {pid})")
20
+ sys.exit(1)
21
+
22
+ with open(self.path, "w") as f:
23
+ f.write(str(os.getpid()))
24
+
25
+ def remove(self):
26
+ if os.path.exists(self.path):
27
+ os.remove(self.path)
28
+
29
+ def _pid_running(self, pid: int):
30
+
31
+ try:
32
+ os.kill(pid, 0)
33
+ return True
34
+ except OSError:
35
+ return False
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.7.1"
3
+ version = "3.7.2.dev0"
4
4
  description = "Aplicativo CLI para exibir gráficos do MetaTrader 5 screen reader friendly"
5
5
  authors = [
6
6
  {name = "Valmir França",email = "vfranca3@gmail.com"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes