mtcli 3.6.0.dev0__tar.gz → 3.6.1__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 (72) hide show
  1. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/PKG-INFO +1 -1
  2. mtcli-3.6.1/mtcli/cli.py +34 -0
  3. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/conf.py +199 -199
  4. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/csv.py +31 -31
  5. mtcli-3.6.1/mtcli/logger.py +136 -0
  6. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/consecutive_bars_model.py +77 -77
  7. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/unconsecutive_bar_model.py +67 -67
  8. mtcli-3.6.1/mtcli/plugin_loader.py +97 -0
  9. mtcli-3.6.1/mtcli/plugins/exemplo.py-dist +30 -0
  10. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/cli.py +98 -98
  11. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/test_mm.py +13 -13
  12. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/cli.py +32 -32
  13. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/models/average_range_model.py +29 -29
  14. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/tests/test_rm.py +11 -11
  15. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/cli.py +41 -41
  16. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/models/model_average_volume.py +31 -31
  17. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/tests/test_vm.py +21 -21
  18. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/close_view.py +37 -37
  19. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/full_view.py +65 -65
  20. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/high_view.py +37 -37
  21. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/low_view.py +37 -37
  22. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/min_view.py +42 -42
  23. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/open_view.py +37 -37
  24. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/ranges_view.py +41 -41
  25. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/rates_view.py +41 -41
  26. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/pyproject.toml +1 -1
  27. mtcli-3.6.0.dev0/mtcli/cli.py +0 -32
  28. mtcli-3.6.0.dev0/mtcli/logger.py +0 -47
  29. mtcli-3.6.0.dev0/mtcli/plugin_loader.py +0 -57
  30. mtcli-3.6.0.dev0/mtcli/plugins/hello.py +0 -20
  31. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/LICENSE +0 -0
  32. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/README.md +0 -0
  33. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/__init__.py +0 -0
  34. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/commands/__init__.py +0 -0
  35. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/commands/bars.py +0 -0
  36. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/conecta.py +0 -0
  37. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/__init__.py +0 -0
  38. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/base.py +0 -0
  39. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/data/mt5.py +0 -0
  40. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/database.py +0 -0
  41. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/domain/__init__.py +0 -0
  42. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/domain/timeframe.py +0 -0
  43. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/__init__.py +0 -0
  44. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/tick_cache.py +0 -0
  45. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/marketdata/tick_repository.py +0 -0
  46. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/__init__.py +0 -0
  47. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/bar_model.py +0 -0
  48. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/bars_model.py +0 -0
  49. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/chart_model.py +0 -0
  50. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/conf_model.py +0 -0
  51. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/rates_model.py +0 -0
  52. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/models/signals_model.py +0 -0
  53. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/mt5_context.py +0 -0
  54. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugin.py +0 -0
  55. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/__init__.py +0 -0
  56. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/__init__.py +0 -0
  57. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/conf.py +0 -0
  58. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  59. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  60. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  61. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  62. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/__init__.py +0 -0
  63. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/conf.py +0 -0
  64. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  65. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  66. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/__init__.py +0 -0
  67. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/conf.py +0 -0
  68. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  69. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  70. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/__init__.py +0 -0
  71. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/vars_view.py +0 -0
  72. {mtcli-3.6.0.dev0 → mtcli-3.6.1}/mtcli/views/volumes_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli
3
- Version: 3.6.0.dev0
3
+ Version: 3.6.1
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
@@ -0,0 +1,34 @@
1
+ """
2
+ CLI principal do mtcli.
3
+
4
+ Este módulo define o grupo principal `mt`
5
+ e inicializa o carregamento de plugins.
6
+ """
7
+
8
+ import click
9
+
10
+ from mtcli.plugin_loader import load_plugins
11
+ from .commands.bars import bars
12
+
13
+
14
+ @click.group(context_settings={"max_content_width": 120})
15
+ @click.version_option(package_name="mtcli")
16
+ def mt():
17
+ """
18
+ CLI principal do mtcli.
19
+
20
+ Exibe gráficos e informações de mercado
21
+ em formato textual compatível com leitores de tela.
22
+ """
23
+ pass
24
+
25
+
26
+ mt.add_command(bars, name="bars")
27
+
28
+
29
+ # Carrega plugins automaticamente
30
+ load_plugins(mt)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ mt()
@@ -1,199 +1,199 @@
1
- """
2
- Configurações principais do mtcli.
3
-
4
- Este módulo centraliza a leitura de configurações provenientes de:
5
-
6
- 1. Variáveis de ambiente
7
- 2. Arquivo mtcli.ini
8
-
9
- As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
10
-
11
- Também fornece utilidades para obter o caminho de arquivos do
12
- terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
13
-
14
- O acesso ao terminal MT5 é realizado apenas sob demanda para evitar
15
- efeitos colaterais durante a importação do módulo (import side effects),
16
- facilitando testes automatizados e execução em ambientes CI/CD.
17
- """
18
-
19
- import os
20
- import configparser
21
-
22
- import MetaTrader5 as mt5
23
-
24
- from mtcli.mt5_context import mt5_conexao
25
-
26
- # ---------------------------------------------------------
27
- # Carregamento do arquivo de configuração
28
- # ---------------------------------------------------------
29
-
30
- CONFIG_FILE = "mtcli.ini"
31
- SECTION = "DEFAULT"
32
-
33
- config = configparser.ConfigParser()
34
- config.read(CONFIG_FILE)
35
-
36
-
37
- def get_config_value(key: str, cast=None, fallback=None):
38
- """
39
- Retorna um valor de configuração.
40
-
41
- A prioridade de leitura é:
42
- 1. Variável de ambiente
43
- 2. Arquivo mtcli.ini
44
- 3. Valor fallback
45
-
46
- Args:
47
- key (str): Nome da chave de configuração.
48
- cast (type | None): Tipo de conversão (ex: int, float).
49
- fallback (Any): Valor padrão caso não encontrado.
50
-
51
- Returns:
52
- Any: valor convertido ou fallback.
53
- """
54
- value = os.getenv(key)
55
-
56
- if value is None:
57
- try:
58
- if cast == int:
59
- value = config.getint(SECTION, key, fallback=fallback)
60
- elif cast == float:
61
- value = config.getfloat(SECTION, key, fallback=fallback)
62
- else:
63
- value = config.get(SECTION, key, fallback=fallback)
64
- except (configparser.NoOptionError, ValueError):
65
- value = fallback
66
- else:
67
- if cast:
68
- try:
69
- value = cast(value)
70
- except ValueError:
71
- value = fallback
72
-
73
- return value
74
-
75
-
76
- # ---------------------------------------------------------
77
- # Configurações gerais
78
- # ---------------------------------------------------------
79
-
80
- SYMBOL = get_config_value("symbol", fallback="WIN$N")
81
- DIGITOS = get_config_value("digitos", cast=int, fallback=2)
82
- PERIOD = get_config_value("period", fallback="D1")
83
- BARS = get_config_value("count", cast=int, fallback=999)
84
-
85
- VIEW = get_config_value("view", fallback="ch")
86
- VOLUME = get_config_value("volume", fallback="tick")
87
- DATE = get_config_value("date", fallback="")
88
-
89
- # ---------------------------------------------------------
90
- # Configurações de leitura de candles
91
- # ---------------------------------------------------------
92
-
93
- LATERAL = get_config_value("lateral", fallback="doji")
94
- ALTA = get_config_value("alta", fallback="verde")
95
- BAIXA = get_config_value("baixa", fallback="vermelho")
96
-
97
- ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
98
- ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
99
-
100
- PERCENTUAL_ROMPIMENTO = get_config_value(
101
- "percentual_rompimento", cast=int, fallback=50
102
- )
103
-
104
- PERCENTUAL_DOJI = get_config_value(
105
- "percentual_doji", cast=int, fallback=10
106
- )
107
-
108
- # ---------------------------------------------------------
109
- # Configurações de padrões de barra
110
- # ---------------------------------------------------------
111
-
112
- UP_BAR = get_config_value("up_bar", fallback="asc")
113
- DOWN_BAR = get_config_value("down_bar", fallback="desc")
114
-
115
- INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
116
- OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
117
-
118
- SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
119
- SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
120
-
121
- # ---------------------------------------------------------
122
- # Fonte de dados
123
- # ---------------------------------------------------------
124
-
125
- DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
126
-
127
- # caminho inicial (pode vir do ini/env)
128
- _INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
129
-
130
-
131
- def get_csv_path():
132
- """
133
- Retorna o caminho da pasta de arquivos do MT5 ou CSV.
134
-
135
- Se o caminho não estiver definido no mtcli.ini ou nas
136
- variáveis de ambiente, o terminal MT5 é consultado para
137
- descobrir automaticamente a pasta MQL5/Files.
138
-
139
- Returns:
140
- str: caminho normalizado da pasta de arquivos.
141
- """
142
- if _INITIAL_CSV_PATH:
143
- path = _INITIAL_CSV_PATH
144
- else:
145
- with mt5_conexao():
146
- terminal_info = mt5.terminal_info()
147
-
148
- if terminal_info is None:
149
- raise RuntimeError(
150
- "Não foi possível obter as informações do terminal MT5."
151
- )
152
-
153
- path = os.path.join(terminal_info.data_path, "MQL5", "Files")
154
-
155
- return os.path.normpath(path) + os.sep
156
-
157
-
158
- # ---------------------------------------------------------
159
- # Factory de DataSource
160
- # ---------------------------------------------------------
161
-
162
- def get_data_source(source=None):
163
- """
164
- Retorna a fonte de dados configurada.
165
-
166
- Args:
167
- source (str | None): sobrescreve DATA_SOURCE se fornecido.
168
-
169
- Returns:
170
- CsvDataSource | MT5DataSource
171
-
172
- Raises:
173
- ValueError: se a fonte de dados não for reconhecida.
174
- """
175
- from mtcli.data import CsvDataSource, MT5DataSource
176
-
177
- src = source.lower() if source else DATA_SOURCE
178
-
179
- if src == "csv":
180
- return CsvDataSource()
181
-
182
- if src == "mt5":
183
- return MT5DataSource()
184
-
185
- raise ValueError(f"Fonte de dados desconhecida: {src}")
186
-
187
-
188
- # ---------------------------------------------------------
189
- # Timeframes suportados
190
- # ---------------------------------------------------------
191
-
192
- _HOURS = [12, 8, 6, 4, 3, 2, 1]
193
- _MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
194
-
195
- TIMEFRAMES = (
196
- ["mn1", "w1", "d1"]
197
- + [f"h{i}" for i in _HOURS]
198
- + [f"m{i}" for i in _MINUTES]
199
- )
1
+ """
2
+ Configurações principais do mtcli.
3
+
4
+ Este módulo centraliza a leitura de configurações provenientes de:
5
+
6
+ 1. Variáveis de ambiente
7
+ 2. Arquivo mtcli.ini
8
+
9
+ As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
10
+
11
+ Também fornece utilidades para obter o caminho de arquivos do
12
+ terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
13
+
14
+ O acesso ao terminal MT5 é realizado apenas sob demanda para evitar
15
+ efeitos colaterais durante a importação do módulo (import side effects),
16
+ facilitando testes automatizados e execução em ambientes CI/CD.
17
+ """
18
+
19
+ import os
20
+ import configparser
21
+
22
+ import MetaTrader5 as mt5
23
+
24
+ from mtcli.mt5_context import mt5_conexao
25
+
26
+ # ---------------------------------------------------------
27
+ # Carregamento do arquivo de configuração
28
+ # ---------------------------------------------------------
29
+
30
+ CONFIG_FILE = "mtcli.ini"
31
+ SECTION = "DEFAULT"
32
+
33
+ config = configparser.ConfigParser()
34
+ config.read(CONFIG_FILE)
35
+
36
+
37
+ def get_config_value(key: str, cast=None, fallback=None):
38
+ """
39
+ Retorna um valor de configuração.
40
+
41
+ A prioridade de leitura é:
42
+ 1. Variável de ambiente
43
+ 2. Arquivo mtcli.ini
44
+ 3. Valor fallback
45
+
46
+ Args:
47
+ key (str): Nome da chave de configuração.
48
+ cast (type | None): Tipo de conversão (ex: int, float).
49
+ fallback (Any): Valor padrão caso não encontrado.
50
+
51
+ Returns:
52
+ Any: valor convertido ou fallback.
53
+ """
54
+ value = os.getenv(key)
55
+
56
+ if value is None:
57
+ try:
58
+ if cast == int:
59
+ value = config.getint(SECTION, key, fallback=fallback)
60
+ elif cast == float:
61
+ value = config.getfloat(SECTION, key, fallback=fallback)
62
+ else:
63
+ value = config.get(SECTION, key, fallback=fallback)
64
+ except (configparser.NoOptionError, ValueError):
65
+ value = fallback
66
+ else:
67
+ if cast:
68
+ try:
69
+ value = cast(value)
70
+ except ValueError:
71
+ value = fallback
72
+
73
+ return value
74
+
75
+
76
+ # ---------------------------------------------------------
77
+ # Configurações gerais
78
+ # ---------------------------------------------------------
79
+
80
+ SYMBOL = get_config_value("symbol", fallback="WIN$N")
81
+ DIGITOS = get_config_value("digitos", cast=int, fallback=2)
82
+ PERIOD = get_config_value("period", fallback="D1")
83
+ BARS = get_config_value("count", cast=int, fallback=999)
84
+
85
+ VIEW = get_config_value("view", fallback="ch")
86
+ VOLUME = get_config_value("volume", fallback="tick")
87
+ DATE = get_config_value("date", fallback="")
88
+
89
+ # ---------------------------------------------------------
90
+ # Configurações de leitura de candles
91
+ # ---------------------------------------------------------
92
+
93
+ LATERAL = get_config_value("lateral", fallback="doji")
94
+ ALTA = get_config_value("alta", fallback="verde")
95
+ BAIXA = get_config_value("baixa", fallback="vermelho")
96
+
97
+ ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
98
+ ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
99
+
100
+ PERCENTUAL_ROMPIMENTO = get_config_value(
101
+ "percentual_rompimento", cast=int, fallback=50
102
+ )
103
+
104
+ PERCENTUAL_DOJI = get_config_value(
105
+ "percentual_doji", cast=int, fallback=10
106
+ )
107
+
108
+ # ---------------------------------------------------------
109
+ # Configurações de padrões de barra
110
+ # ---------------------------------------------------------
111
+
112
+ UP_BAR = get_config_value("up_bar", fallback="asc")
113
+ DOWN_BAR = get_config_value("down_bar", fallback="desc")
114
+
115
+ INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
116
+ OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
117
+
118
+ SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
119
+ SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
120
+
121
+ # ---------------------------------------------------------
122
+ # Fonte de dados
123
+ # ---------------------------------------------------------
124
+
125
+ DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
126
+
127
+ # caminho inicial (pode vir do ini/env)
128
+ _INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
129
+
130
+
131
+ def get_csv_path():
132
+ """
133
+ Retorna o caminho da pasta de arquivos do MT5 ou CSV.
134
+
135
+ Se o caminho não estiver definido no mtcli.ini ou nas
136
+ variáveis de ambiente, o terminal MT5 é consultado para
137
+ descobrir automaticamente a pasta MQL5/Files.
138
+
139
+ Returns:
140
+ str: caminho normalizado da pasta de arquivos.
141
+ """
142
+ if _INITIAL_CSV_PATH:
143
+ path = _INITIAL_CSV_PATH
144
+ else:
145
+ with mt5_conexao():
146
+ terminal_info = mt5.terminal_info()
147
+
148
+ if terminal_info is None:
149
+ raise RuntimeError(
150
+ "Não foi possível obter as informações do terminal MT5."
151
+ )
152
+
153
+ path = os.path.join(terminal_info.data_path, "MQL5", "Files")
154
+
155
+ return os.path.normpath(path) + os.sep
156
+
157
+
158
+ # ---------------------------------------------------------
159
+ # Factory de DataSource
160
+ # ---------------------------------------------------------
161
+
162
+ def get_data_source(source=None):
163
+ """
164
+ Retorna a fonte de dados configurada.
165
+
166
+ Args:
167
+ source (str | None): sobrescreve DATA_SOURCE se fornecido.
168
+
169
+ Returns:
170
+ CsvDataSource | MT5DataSource
171
+
172
+ Raises:
173
+ ValueError: se a fonte de dados não for reconhecida.
174
+ """
175
+ from mtcli.data import CsvDataSource, MT5DataSource
176
+
177
+ src = source.lower() if source else DATA_SOURCE
178
+
179
+ if src == "csv":
180
+ return CsvDataSource()
181
+
182
+ if src == "mt5":
183
+ return MT5DataSource()
184
+
185
+ raise ValueError(f"Fonte de dados desconhecida: {src}")
186
+
187
+
188
+ # ---------------------------------------------------------
189
+ # Timeframes suportados
190
+ # ---------------------------------------------------------
191
+
192
+ _HOURS = [12, 8, 6, 4, 3, 2, 1]
193
+ _MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
194
+
195
+ TIMEFRAMES = (
196
+ ["mn1", "w1", "d1"]
197
+ + [f"h{i}" for i in _HOURS]
198
+ + [f"m{i}" for i in _MINUTES]
199
+ )
@@ -1,31 +1,31 @@
1
- """Módulo fonte de dados via CSV."""
2
-
3
- import csv
4
- import os
5
-
6
- from mtcli import conf
7
- from mtcli.logger import setup_logger
8
-
9
- from .base import DataSourceBase
10
-
11
- logger = setup_logger()
12
-
13
-
14
- class CsvDataSource(DataSourceBase):
15
- """Fonte de dados via CSV."""
16
-
17
- def get_data(self, symbol, period, count=100):
18
- """Retorna dados CSV em uma lista de lista."""
19
- file_path = os.path.join(conf._INITIAL_CSV_PATH, f"{symbol}{period}.csv")
20
- logger.info(f"Iniciando coleta de dados via CSV: {file_path}.")
21
- csv_data = []
22
- try:
23
- with open(file_path, encoding="utf-16", newline="") as f:
24
- lines = csv.reader(f, delimiter=",", quotechar="'")
25
- for line in lines:
26
- csv_data.append(line)
27
- except:
28
- logger.warning(f"Arquivo {file_path} não encontrado.")
29
- print("Arquivo %s nao encontrado! Tente novamente" % file_path)
30
- logger.info("Coleta de dados via CSV finalizada.")
31
- return csv_data
1
+ """Módulo fonte de dados via CSV."""
2
+
3
+ import csv
4
+ import os
5
+
6
+ from mtcli import conf
7
+ from mtcli.logger import setup_logger
8
+
9
+ from .base import DataSourceBase
10
+
11
+ logger = setup_logger()
12
+
13
+
14
+ class CsvDataSource(DataSourceBase):
15
+ """Fonte de dados via CSV."""
16
+
17
+ def get_data(self, symbol, period, count=100):
18
+ """Retorna dados CSV em uma lista de lista."""
19
+ file_path = os.path.join(conf._INITIAL_CSV_PATH, f"{symbol}{period}.csv")
20
+ logger.info(f"Iniciando coleta de dados via CSV: {file_path}.")
21
+ csv_data = []
22
+ try:
23
+ with open(file_path, encoding="utf-16", newline="") as f:
24
+ lines = csv.reader(f, delimiter=",", quotechar="'")
25
+ for line in lines:
26
+ csv_data.append(line)
27
+ except:
28
+ logger.warning(f"Arquivo {file_path} não encontrado.")
29
+ print("Arquivo %s nao encontrado! Tente novamente" % file_path)
30
+ logger.info("Coleta de dados via CSV finalizada.")
31
+ return csv_data
@@ -0,0 +1,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/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()