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.
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/PKG-INFO +1 -1
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/conf.py +325 -309
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/logger.py +178 -136
- mtcli-3.7.2.dev0/mtcli/utils/__init__.py +0 -0
- mtcli-3.7.2.dev0/mtcli/utils/pidfile.py +35 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/pyproject.toml +1 -1
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/LICENSE +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/README.md +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/cli.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/bars.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/conf.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/commands/doctor.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/conecta.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/config_registre.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/base.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/csv.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/data/mt5.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/database.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/marketdata/tick_repository.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/consecutive_bars_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/models/unconsecutive_bar_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/mt5_context.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin_loader.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugin_manager.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/cli.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/cli.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/__init__.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/close_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/full_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/high_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/low_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/min_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/open_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/ranges_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/rates_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.7.1 → mtcli-3.7.2.dev0}/mtcli/views/volumes_view.py +0 -0
|
@@ -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
|
-
|
|
11
|
-
%APPDATA%/mtcli/logs/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- tamanho máximo: 2 MB
|
|
15
|
-
- até 3 arquivos de backup
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
inicializam o logger múltiplas vezes.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
no Windows).
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|