mtcli 3.6.0.dev1__py3-none-any.whl → 3.7.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mtcli/cli.py CHANGED
@@ -1,34 +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
+ """
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()
mtcli/commands/conf.py ADDED
@@ -0,0 +1,44 @@
1
+ """
2
+ Comando para exibir configurações disponíveis.
3
+ """
4
+
5
+ import click
6
+
7
+ from mtcli.config_registry import registry
8
+ from mtcli.conf import conf
9
+
10
+
11
+ @click.command()
12
+ @click.argument("section", required=False)
13
+ def conf_cmd(section):
14
+ """
15
+ Exibe configurações disponíveis.
16
+
17
+ Exemplos:
18
+
19
+ mt conf
20
+ mt conf renko
21
+ """
22
+
23
+ if section:
24
+ options = registry.get_section(section)
25
+ else:
26
+ options = registry.get_all()
27
+
28
+ if not options:
29
+ click.echo("Nenhuma configuração registrada.")
30
+ return
31
+
32
+ for opt in options:
33
+
34
+ value = conf.get(opt.name, section=opt.section, default=opt.default)
35
+
36
+ click.echo(
37
+ f"{opt.section}.{opt.name} = {value} "
38
+ f"(default={opt.default})"
39
+ )
40
+
41
+ if opt.description:
42
+ click.echo(f" {opt.description}")
43
+
44
+ click.echo()
mtcli/conf.py CHANGED
@@ -1,19 +1,18 @@
1
1
  """
2
- Configurações principais do mtcli.
2
+ Sistema central de configuração do mtcli.
3
3
 
4
- Este módulo centraliza a leitura de configurações provenientes de:
4
+ Fornece leitura de configuração a partir de:
5
5
 
6
6
  1. Variáveis de ambiente
7
7
  2. Arquivo mtcli.ini
8
+ 3. Valores default
8
9
 
9
- As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
10
+ Também oferece utilidades usadas por plugins como:
10
11
 
11
- Também fornece utilidades para obter o caminho de arquivos do
12
- terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
12
+ - descoberta do diretório MQL5/Files
13
+ - seleção da fonte de dados (CSV ou MT5)
13
14
 
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.
15
+ Plugins devem acessar a configuração através do objeto global `conf`.
17
16
  """
18
17
 
19
18
  import os
@@ -23,170 +22,147 @@ import MetaTrader5 as mt5
23
22
 
24
23
  from mtcli.mt5_context import mt5_conexao
25
24
 
26
- # ---------------------------------------------------------
27
- # Carregamento do arquivo de configuração
28
- # ---------------------------------------------------------
29
25
 
30
- CONFIG_FILE = "mtcli.ini"
31
- SECTION = "DEFAULT"
26
+ class Config:
27
+ """
28
+ Gerenciador central de configurações do mtcli.
29
+ """
32
30
 
33
- config = configparser.ConfigParser()
34
- config.read(CONFIG_FILE)
31
+ def __init__(self, filename="mtcli.ini"):
32
+ self.config = configparser.ConfigParser()
33
+ self.config.read(filename)
35
34
 
35
+ # ---------------------------------------------------------
36
+ # leitura de valores
37
+ # ---------------------------------------------------------
36
38
 
37
- def get_config_value(key: str, cast=None, fallback=None):
38
- """
39
- Retorna um valor de configuração.
39
+ def get(self, key, section="DEFAULT", cast=None, default=None):
40
+ """
41
+ Retorna um valor de configuração.
40
42
 
41
- A prioridade de leitura é:
42
- 1. Variável de ambiente
43
- 2. Arquivo mtcli.ini
44
- 3. Valor fallback
43
+ Prioridade:
45
44
 
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.
45
+ 1. Variável de ambiente SECTION_KEY
46
+ 2. Variável de ambiente KEY
47
+ 3. mtcli.ini [section]
48
+ 4. mtcli.ini [DEFAULT]
49
+ 5. default
50
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
51
+ Args:
52
+ key (str)
53
+ section (str)
54
+ cast (type | None)
55
+ default (Any)
72
56
 
73
- return value
57
+ Returns:
58
+ Any
59
+ """
74
60
 
61
+ env_key = f"{section.upper()}_{key.upper()}"
75
62
 
76
- # ---------------------------------------------------------
77
- # Configurações gerais
78
- # ---------------------------------------------------------
63
+ value = os.getenv(env_key) or os.getenv(key.upper())
79
64
 
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)
65
+ if value is None:
84
66
 
85
- VIEW = get_config_value("view", fallback="ch")
86
- VOLUME = get_config_value("volume", fallback="tick")
87
- DATE = get_config_value("date", fallback="")
67
+ if self.config.has_option(section, key):
68
+ value = self.config.get(section, key)
88
69
 
89
- # ---------------------------------------------------------
90
- # Configurações de leitura de candles
91
- # ---------------------------------------------------------
70
+ elif self.config.has_option("DEFAULT", key):
71
+ value = self.config.get("DEFAULT", key)
92
72
 
93
- LATERAL = get_config_value("lateral", fallback="doji")
94
- ALTA = get_config_value("alta", fallback="verde")
95
- BAIXA = get_config_value("baixa", fallback="vermelho")
73
+ else:
74
+ value = default
96
75
 
97
- ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
98
- ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
76
+ if cast and value is not None:
99
77
 
100
- PERCENTUAL_ROMPIMENTO = get_config_value(
101
- "percentual_rompimento", cast=int, fallback=50
102
- )
78
+ try:
103
79
 
104
- PERCENTUAL_DOJI = get_config_value(
105
- "percentual_doji", cast=int, fallback=10
106
- )
80
+ if cast is bool:
81
+ value = str(value).lower() in ("1", "true", "yes")
107
82
 
108
- # ---------------------------------------------------------
109
- # Configurações de padrões de barra
110
- # ---------------------------------------------------------
83
+ else:
84
+ value = cast(value)
111
85
 
112
- UP_BAR = get_config_value("up_bar", fallback="asc")
113
- DOWN_BAR = get_config_value("down_bar", fallback="desc")
86
+ except ValueError:
87
+ value = default
114
88
 
115
- INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
116
- OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
89
+ return value
117
90
 
118
- SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
119
- SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
91
+ # ---------------------------------------------------------
92
+ # seção helper
93
+ # ---------------------------------------------------------
120
94
 
121
- # ---------------------------------------------------------
122
- # Fonte de dados
123
- # ---------------------------------------------------------
95
+ def section(self, section):
96
+ """
97
+ Retorna um helper para acessar uma seção específica.
98
+ """
124
99
 
125
- DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
100
+ class Section:
126
101
 
127
- # caminho inicial (pode vir do ini/env)
128
- _INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
102
+ def __init__(self, parent, section):
103
+ self.parent = parent
104
+ self.section = section
129
105
 
106
+ def get(self, key, cast=None, default=None):
107
+ return self.parent.get(key, self.section, cast, default)
130
108
 
131
- def get_csv_path():
132
- """
133
- Retorna o caminho da pasta de arquivos do MT5 ou CSV.
109
+ return Section(self, section)
134
110
 
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.
111
+ # ---------------------------------------------------------
112
+ # caminho MT5
113
+ # ---------------------------------------------------------
114
+
115
+ def get_csv_path(self):
116
+ """
117
+ Retorna o caminho da pasta MQL5/Files.
118
+ """
119
+
120
+ path = self.get("mt5_pasta")
121
+
122
+ if path:
123
+ return os.path.normpath(path) + os.sep
138
124
 
139
- Returns:
140
- str: caminho normalizado da pasta de arquivos.
141
- """
142
- if _INITIAL_CSV_PATH:
143
- path = _INITIAL_CSV_PATH
144
- else:
145
125
  with mt5_conexao():
146
- terminal_info = mt5.terminal_info()
147
126
 
148
- if terminal_info is None:
127
+ info = mt5.terminal_info()
128
+
129
+ if info is None:
149
130
  raise RuntimeError(
150
- "Não foi possível obter as informações do terminal MT5."
131
+ "Não foi possível obter informações do terminal MT5."
151
132
  )
152
133
 
153
- path = os.path.join(terminal_info.data_path, "MQL5", "Files")
154
-
155
- return os.path.normpath(path) + os.sep
134
+ path = os.path.join(info.data_path, "MQL5", "Files")
156
135
 
136
+ return os.path.normpath(path) + os.sep
157
137
 
158
- # ---------------------------------------------------------
159
- # Factory de DataSource
160
- # ---------------------------------------------------------
138
+ # ---------------------------------------------------------
139
+ # data source
140
+ # ---------------------------------------------------------
161
141
 
162
- def get_data_source(source=None):
163
- """
164
- Retorna a fonte de dados configurada.
142
+ def get_data_source(self, source=None):
143
+ """
144
+ Retorna a fonte de dados configurada.
145
+ """
165
146
 
166
- Args:
167
- source (str | None): sobrescreve DATA_SOURCE se fornecido.
147
+ from mtcli.data import CsvDataSource, MT5DataSource
168
148
 
169
- Returns:
170
- CsvDataSource | MT5DataSource
149
+ src = (source or self.get("dados", default="mt5")).lower()
171
150
 
172
- Raises:
173
- ValueError: se a fonte de dados não for reconhecida.
174
- """
175
- from mtcli.data import CsvDataSource, MT5DataSource
151
+ if src == "csv":
152
+ return CsvDataSource()
176
153
 
177
- src = source.lower() if source else DATA_SOURCE
154
+ if src == "mt5":
155
+ return MT5DataSource()
178
156
 
179
- if src == "csv":
180
- return CsvDataSource()
157
+ raise ValueError(f"Fonte de dados desconhecida: {src}")
181
158
 
182
- if src == "mt5":
183
- return MT5DataSource()
184
159
 
185
- raise ValueError(f"Fonte de dados desconhecida: {src}")
160
+ # instância global usada por todo o sistema
161
+ conf = Config()
186
162
 
187
163
 
188
164
  # ---------------------------------------------------------
189
- # Timeframes suportados
165
+ # timeframes suportados
190
166
  # ---------------------------------------------------------
191
167
 
192
168
  _HOURS = [12, 8, 6, 4, 3, 2, 1]
@@ -197,3 +173,61 @@ TIMEFRAMES = (
197
173
  + [f"h{i}" for i in _HOURS]
198
174
  + [f"m{i}" for i in _MINUTES]
199
175
  )
176
+
177
+
178
+ # ---------------------------------------------------------
179
+ # Configurações gerais
180
+ # ---------------------------------------------------------
181
+
182
+ SYMBOL = conf.get("symbol", default="WIN$N")
183
+ DIGITOS = conf.get("digitos", cast=int, default=2)
184
+ PERIOD = conf.get("period", default="D1")
185
+ BARS = conf.get("count", cast=int, default=999)
186
+
187
+ VIEW = conf.get("view", default="ch")
188
+ VOLUME = conf.get("volume", default="tick")
189
+ DATE = conf.get("date", default="")
190
+
191
+ # ---------------------------------------------------------
192
+ # Configurações de leitura de candles
193
+ # ---------------------------------------------------------
194
+
195
+ LATERAL = conf.get("lateral", default="doji")
196
+ ALTA = conf.get("alta", default="verde")
197
+ BAIXA = conf.get("baixa", default="vermelho")
198
+
199
+ ROMPIMENTO_ALTA = conf.get("rompimento_alta", default="c")
200
+ ROMPIMENTO_BAIXA = conf.get("rompimento_baixa", default="v")
201
+
202
+ PERCENTUAL_ROMPIMENTO = conf.get(
203
+ "percentual_rompimento", cast=int, default=50
204
+ )
205
+
206
+ PERCENTUAL_DOJI = conf.get(
207
+ "percentual_doji", cast=int, default=10
208
+ )
209
+
210
+ # ---------------------------------------------------------
211
+ # Configurações de padrões de barra
212
+ # ---------------------------------------------------------
213
+
214
+ UP_BAR = conf.get("up_bar", default="asc")
215
+ DOWN_BAR = conf.get("down_bar", default="desc")
216
+
217
+ INSIDE_BAR = conf.get("inside_bar", default="ib")
218
+ OUTSIDE_BAR = conf.get("outside_bar", default="ob")
219
+
220
+ SOMBRA_SUPERIOR = conf.get("sombra_superior", default="top")
221
+ SOMBRA_INFERIOR = conf.get("sombra_inferior", default="bottom")
222
+
223
+ # ---------------------------------------------------------
224
+ # Fonte de dados
225
+ # ---------------------------------------------------------
226
+
227
+ DATA_SOURCE = conf.get_data_source()
228
+
229
+ # ---------------------------------------------------------
230
+ # caminho inicial do CSV (pode vir do ini/env)
231
+ # ---------------------------------------------------------
232
+
233
+ _INITIAL_CSV_PATH = conf.get_csv_path()
@@ -0,0 +1,41 @@
1
+ """
2
+ Registry de configurações do mtcli.
3
+
4
+ Plugins podem registrar suas opções de configuração aqui
5
+ para permitir descoberta automática via CLI.
6
+ """
7
+
8
+
9
+ class ConfigOption:
10
+ """
11
+ Representa uma opção de configuração registrada.
12
+ """
13
+
14
+ def __init__(self, section, name, type=str, default=None, description=""):
15
+ self.section = section
16
+ self.name = name
17
+ self.type = type
18
+ self.default = default
19
+ self.description = description
20
+
21
+
22
+ class ConfigRegistry:
23
+ """
24
+ Registro central de opções de configuração.
25
+ """
26
+
27
+ def __init__(self):
28
+ self._options = []
29
+
30
+ def register(self, section, name, type=str, default=None, description=""):
31
+ option = ConfigOption(section, name, type, default, description)
32
+ self._options.append(option)
33
+
34
+ def get_all(self):
35
+ return self._options
36
+
37
+ def get_section(self, section):
38
+ return [o for o in self._options if o.section == section]
39
+
40
+
41
+ registry = ConfigRegistry()
mtcli/data/csv.py CHANGED
@@ -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