mtcli 3.2.1__tar.gz → 3.3.0.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/PKG-INFO +1 -1
  2. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/commands/bars.py +147 -147
  3. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/commands/conf.py +1 -1
  4. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/commands/logs.py +42 -40
  5. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/conecta.py +1 -0
  6. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/conf.py +102 -101
  7. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/data/mt5.py +0 -2
  8. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/logger.py +47 -45
  9. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/conf_model.py +1 -2
  10. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/signals_model.py +0 -2
  11. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/mt5_context.py +2 -1
  12. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/models/model_media_movel.py +22 -25
  13. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/command.py +1 -0
  14. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/close_view.py +1 -1
  15. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/high_view.py +1 -1
  16. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/low_view.py +1 -1
  17. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/open_view.py +1 -1
  18. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/ranges_view.py +1 -3
  19. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/vars_view.py +1 -8
  20. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/volumes_view.py +1 -2
  21. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/pyproject.toml +28 -3
  22. mtcli-3.2.1/mtcli/plugins/agressao/__init__.py +0 -4
  23. mtcli-3.2.1/mtcli/plugins/agressao/command.py +0 -45
  24. mtcli-3.2.1/mtcli/plugins/agressao/models/__init__.py +0 -1
  25. mtcli-3.2.1/mtcli/plugins/agressao/models/model_agressao.py +0 -39
  26. mtcli-3.2.1/mtcli/plugins/agressao/views/__init__.py +0 -1
  27. mtcli-3.2.1/mtcli/plugins/agressao/views/view_agressao.py +0 -15
  28. mtcli-3.2.1/mtcli/plugins/volume_medio/conf.py +0 -1
  29. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/LICENSE +0 -0
  30. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/README.md +0 -0
  31. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/__init__.py +0 -0
  32. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/commands/__init__.py +0 -0
  33. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/data/__init__.py +0 -0
  34. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/data/base.py +0 -0
  35. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/data/csv.py +0 -0
  36. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/__init__.py +0 -0
  37. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/bar_model.py +0 -0
  38. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/bars_model.py +0 -0
  39. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/chart_model.py +0 -0
  40. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/consecutive_bars_model.py +0 -0
  41. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/rates_model.py +0 -0
  42. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/models/unconsecutive_bar_model.py +0 -0
  43. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/mt.py +0 -0
  44. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugin.py +0 -0
  45. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/__init__.py +0 -0
  46. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/__init__.py +0 -0
  47. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/command.py +0 -0
  48. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/conf.py +0 -0
  49. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  50. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  51. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  52. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  53. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/__init__.py +0 -0
  54. {mtcli-3.2.1/mtcli/plugins/agressao → mtcli-3.3.0.dev0/mtcli/plugins/range_medio}/conf.py +0 -0
  55. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  56. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  57. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  58. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  59. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/__init__.py +0 -0
  60. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/command.py +0 -0
  61. {mtcli-3.2.1/mtcli/plugins/range_medio → mtcli-3.3.0.dev0/mtcli/plugins/volume_medio}/conf.py +0 -0
  62. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  63. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  64. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  65. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  66. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/__init__.py +0 -0
  67. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/full_view.py +0 -0
  68. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/min_view.py +0 -0
  69. {mtcli-3.2.1 → mtcli-3.3.0.dev0}/mtcli/views/rates_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mtcli
3
- Version: 3.2.1
3
+ Version: 3.3.0.dev0
4
4
  Summary: Aplicativo CLI para exibir gráficos do MetaTrader 5 em texto acessível ao leitor de telas
5
5
  License: GPL-3.0
6
6
  Keywords: MetaTrader 5,trading,CLI
@@ -1,147 +1,147 @@
1
- """Exibe o gráfico de barras."""
2
-
3
- import click
4
-
5
- from mtcli.models.rates_model import RatesModel
6
- from mtcli.models.bars_model import BarsModel
7
- from mtcli.views.full_view import FullView
8
- from mtcli.views.min_view import MinView
9
- from mtcli.views.ranges_view import RangesView
10
- from mtcli.views.volumes_view import VolumesView
11
- from mtcli.views.vars_view import VarsView
12
- from mtcli.views.rates_view import RatesView
13
- from mtcli.views.open_view import OpenView
14
- from mtcli.views.high_view import HighView
15
- from mtcli.views.low_view import LowView
16
- from mtcli.views.close_view import CloseView
17
- from mtcli.conf import (
18
- VIEW,
19
- PERIOD,
20
- BARS,
21
- DATE,
22
- VOLUME,
23
- TIMEFRAMES,
24
- )
25
-
26
-
27
- @click.command(
28
- "bars",
29
- help="Mostra o gráfico de candles em texto para o ativo e período especificados.",
30
- )
31
- @click.argument("symbol")
32
- @click.option(
33
- "--view",
34
- "-v",
35
- type=click.Choice(
36
- [
37
- "ch",
38
- "m",
39
- "hl",
40
- "f",
41
- "full",
42
- "r",
43
- "range",
44
- "v",
45
- "volume",
46
- "va",
47
- "percentual",
48
- "oh",
49
- "ohlc",
50
- "o",
51
- "open",
52
- "h",
53
- "high",
54
- "l",
55
- "low",
56
- "c",
57
- "close",
58
- ],
59
- case_sensitive=False,
60
- ),
61
- default=VIEW,
62
- show_default=True,
63
- help="Formato de exibicao. Opcoes: minima ou HL - minima; f - completa; r - ranges; v - volumes; va - variações percentuais; oh - OHLC; o - aberturas; h - maximas; l - minimas; c - fechamentos.",
64
- )
65
- @click.option(
66
- "--period",
67
- "-p",
68
- type=click.Choice(TIMEFRAMES, case_sensitive=False),
69
- default=PERIOD,
70
- show_default=True,
71
- help="Timeframe das barras.",
72
- )
73
- @click.option(
74
- "--count",
75
- "-c",
76
- type=int,
77
- default=BARS,
78
- show_default=True,
79
- help="Numero de barras.",
80
- )
81
- @click.option(
82
- "--date",
83
- "-d",
84
- default=DATE,
85
- show_default=True,
86
- help="Data para intraday, formato AAAA-MM-DD.",
87
- )
88
- @click.option(
89
- "--numerator",
90
- "-n",
91
- is_flag=True,
92
- default=False,
93
- show_default=True,
94
- help="Ativa a numeracao das barras.",
95
- )
96
- @click.option(
97
- "--show-date",
98
- "-sd",
99
- is_flag=True,
100
- default=False,
101
- show_default=True,
102
- help="Ativa a datacao das barras.",
103
- )
104
- @click.option(
105
- "--volume",
106
- "-vo",
107
- type=click.Choice(["tick", "real"], case_sensitive=False),
108
- default=VOLUME,
109
- show_default=True,
110
- help="Tipo de volume.",
111
- )
112
- def bars(symbol, view, period, count, date, numerator, show_date, volume):
113
- """Exibe o grafico do MetaTrader 5."""
114
- period = period.lower()
115
- view = view.lower()
116
- rates = RatesModel(symbol, period, count).get_data()
117
- bars = BarsModel(rates, date).get_bars()
118
- views = []
119
- if view in ["m", "ch", "hl"]: # máximas e mínimas
120
- views = MinView(bars, count, period, date, numerator, show_date).views()
121
- elif view in ["r", "range"]: # ranges
122
- views = RangesView(bars, count, period, date, numerator, show_date).views()
123
- elif view in ["oh", "ohlc"]: # OHLC
124
- views = RatesView(bars, count, period, date, numerator, show_date).views()
125
- elif view in ["va", "percentual"]: # variações percentuais
126
- views = VarsView(bars, count, period, date, numerator, show_date).views()
127
- elif view in ["o", "open"]: # abertura
128
- views = OpenView(bars, count, period, date, numerator, show_date).views()
129
- elif view in ["h", "high"]: # máximas
130
- views = HighView(bars, count, period, date, numerator, show_date).views()
131
- elif view in ["l", "low"]: # mínimas
132
- views = LowView(bars, count, period, date, numerator, show_date).views()
133
- elif view in ["c", "close"]: # fechamentos
134
- views = CloseView(bars, count, period, date, numerator, show_date).views()
135
- elif view in ["v", "volume"]: # volumes
136
- views = VolumesView(
137
- bars, count, period, date, numerator, show_date, volume
138
- ).views()
139
- else: # completo
140
- views = FullView(bars, count, period, date, numerator, show_date).views()
141
- if views:
142
- for view in views:
143
- click.echo(view)
144
-
145
-
146
- if __name__ == "__main__":
147
- bars()
1
+ """Exibe o gráfico de barras."""
2
+
3
+ import click
4
+
5
+ from mtcli.conf import (
6
+ BARS,
7
+ DATE,
8
+ PERIOD,
9
+ TIMEFRAMES,
10
+ VIEW,
11
+ VOLUME,
12
+ )
13
+ from mtcli.models.bars_model import BarsModel
14
+ from mtcli.models.rates_model import RatesModel
15
+ from mtcli.views.close_view import CloseView
16
+ from mtcli.views.full_view import FullView
17
+ from mtcli.views.high_view import HighView
18
+ from mtcli.views.low_view import LowView
19
+ from mtcli.views.min_view import MinView
20
+ from mtcli.views.open_view import OpenView
21
+ from mtcli.views.ranges_view import RangesView
22
+ from mtcli.views.rates_view import RatesView
23
+ from mtcli.views.vars_view import VarsView
24
+ from mtcli.views.volumes_view import VolumesView
25
+
26
+
27
+ @click.command(
28
+ "bars",
29
+ help="Mostra o gráfico de candles em texto para o ativo e período especificados.",
30
+ )
31
+ @click.argument("symbol")
32
+ @click.option(
33
+ "--view",
34
+ "-v",
35
+ type=click.Choice(
36
+ [
37
+ "ch",
38
+ "m",
39
+ "hl",
40
+ "f",
41
+ "full",
42
+ "r",
43
+ "range",
44
+ "v",
45
+ "volume",
46
+ "va",
47
+ "percentual",
48
+ "oh",
49
+ "ohlc",
50
+ "o",
51
+ "open",
52
+ "h",
53
+ "high",
54
+ "l",
55
+ "low",
56
+ "c",
57
+ "close",
58
+ ],
59
+ case_sensitive=False,
60
+ ),
61
+ default=VIEW,
62
+ show_default=True,
63
+ help="Formato de exibicao. Opcoes: minima ou HL - minima; f - completa; r - ranges; v - volumes; va - variações percentuais; oh - OHLC; o - aberturas; h - maximas; l - minimas; c - fechamentos.",
64
+ )
65
+ @click.option(
66
+ "--period",
67
+ "-p",
68
+ type=click.Choice(TIMEFRAMES, case_sensitive=False),
69
+ default=PERIOD,
70
+ show_default=True,
71
+ help="Timeframe das barras.",
72
+ )
73
+ @click.option(
74
+ "--count",
75
+ "-c",
76
+ type=int,
77
+ default=BARS,
78
+ show_default=True,
79
+ help="Numero de barras.",
80
+ )
81
+ @click.option(
82
+ "--date",
83
+ "-d",
84
+ default=DATE,
85
+ show_default=True,
86
+ help="Data para intraday, formato AAAA-MM-DD.",
87
+ )
88
+ @click.option(
89
+ "--numerator",
90
+ "-n",
91
+ is_flag=True,
92
+ default=False,
93
+ show_default=True,
94
+ help="Ativa a numeracao das barras.",
95
+ )
96
+ @click.option(
97
+ "--show-date",
98
+ "-sd",
99
+ is_flag=True,
100
+ default=False,
101
+ show_default=True,
102
+ help="Ativa a datacao das barras.",
103
+ )
104
+ @click.option(
105
+ "--volume",
106
+ "-vo",
107
+ type=click.Choice(["tick", "real"], case_sensitive=False),
108
+ default=VOLUME,
109
+ show_default=True,
110
+ help="Tipo de volume.",
111
+ )
112
+ def bars(symbol, view, period, count, date, numerator, show_date, volume):
113
+ """Exibe o grafico do MetaTrader 5."""
114
+ period = period.lower()
115
+ view = view.lower()
116
+ rates = RatesModel(symbol, period, count).get_data()
117
+ bars = BarsModel(rates, date).get_bars()
118
+ views = []
119
+ if view in ["m", "ch", "hl"]: # máximas e mínimas
120
+ views = MinView(bars, count, period, date, numerator, show_date).views()
121
+ elif view in ["r", "range"]: # ranges
122
+ views = RangesView(bars, count, period, date, numerator, show_date).views()
123
+ elif view in ["oh", "ohlc"]: # OHLC
124
+ views = RatesView(bars, count, period, date, numerator, show_date).views()
125
+ elif view in ["va", "percentual"]: # variações percentuais
126
+ views = VarsView(bars, count, period, date, numerator, show_date).views()
127
+ elif view in ["o", "open"]: # abertura
128
+ views = OpenView(bars, count, period, date, numerator, show_date).views()
129
+ elif view in ["h", "high"]: # máximas
130
+ views = HighView(bars, count, period, date, numerator, show_date).views()
131
+ elif view in ["l", "low"]: # mínimas
132
+ views = LowView(bars, count, period, date, numerator, show_date).views()
133
+ elif view in ["c", "close"]: # fechamentos
134
+ views = CloseView(bars, count, period, date, numerator, show_date).views()
135
+ elif view in ["v", "volume"]: # volumes
136
+ views = VolumesView(
137
+ bars, count, period, date, numerator, show_date, volume
138
+ ).views()
139
+ else: # completo
140
+ views = FullView(bars, count, period, date, numerator, show_date).views()
141
+ if views:
142
+ for view in views:
143
+ click.echo(view)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ bars()
@@ -1,7 +1,7 @@
1
1
  """Gerencia configurações registradas no mtcli.ini."""
2
2
 
3
- import os
4
3
  import click
4
+
5
5
  from mtcli.models.conf_model import ConfModel
6
6
 
7
7
 
@@ -1,40 +1,42 @@
1
- """Comando logs para exibição dos logs."""
2
-
3
- import os
4
- import click
5
- from mtcli.logger import setup_logger
6
-
7
-
8
- @click.command("logs", help="Exibe ou limpa os logs de execução do mtcli.")
9
- @click.option("--clear", is_flag=True, help="Limpa o arquivo de log.")
10
- @click.option("--tail", default=0, type=int, help="Mostra apenas as últimas N linhas.")
11
- @click.option("--grep", default="", help="Filtra o log por uma palavra-chave.")
12
- def logs(clear, tail, grep):
13
- """Exibe ou limpa os registros de log do mtcli."""
14
- logger = setup_logger()
15
- log_file = logger.handlers[0].baseFilename
16
-
17
- if clear:
18
- if os.path.exists(log_file):
19
- open(log_file, "w", encoding="utf-8").close()
20
- click.echo("Log limpo com sucesso.")
21
- else:
22
- click.echo("Nenhum log para limpar.")
23
- return
24
-
25
- if not os.path.exists(log_file):
26
- click.echo("Nenhum log encontrado.")
27
- return
28
-
29
- with open(log_file, encoding="utf-8", errors="ignore") as f:
30
- linhas = f.readlines()
31
-
32
- if tail > 0:
33
- linhas = linhas[-tail:]
34
- if grep:
35
- linhas = [linha for linha in linhas if grep.lower() in linha.lower()]
36
-
37
- if not linhas:
38
- click.echo("Nenhuma linha encontrada com os filtros aplicados.")
39
- else:
40
- click.echo_via_pager("".join(linhas))
1
+ """Comando logs para exibição dos logs."""
2
+
3
+ import os
4
+
5
+ import click
6
+
7
+ from mtcli.logger import setup_logger
8
+
9
+
10
+ @click.command("logs", help="Exibe ou limpa os logs de execução do mtcli.")
11
+ @click.option("--clear", is_flag=True, help="Limpa o arquivo de log.")
12
+ @click.option("--tail", default=0, type=int, help="Mostra apenas as últimas N linhas.")
13
+ @click.option("--grep", default="", help="Filtra o log por uma palavra-chave.")
14
+ def logs(clear, tail, grep):
15
+ """Exibe ou limpa os registros de log do mtcli."""
16
+ logger = setup_logger()
17
+ log_file = logger.handlers[0].baseFilename
18
+
19
+ if clear:
20
+ if os.path.exists(log_file):
21
+ open(log_file, "w", encoding="utf-8").close()
22
+ click.echo("Log limpo com sucesso.")
23
+ else:
24
+ click.echo("Nenhum log para limpar.")
25
+ return
26
+
27
+ if not os.path.exists(log_file):
28
+ click.echo("Nenhum log encontrado.")
29
+ return
30
+
31
+ with open(log_file, encoding="utf-8", errors="ignore") as f:
32
+ linhas = f.readlines()
33
+
34
+ if tail > 0:
35
+ linhas = linhas[-tail:]
36
+ if grep:
37
+ linhas = [linha for linha in linhas if grep.lower() in linha.lower()]
38
+
39
+ if not linhas:
40
+ click.echo("Nenhuma linha encontrada com os filtros aplicados.")
41
+ else:
42
+ click.echo_via_pager("".join(linhas))
@@ -4,6 +4,7 @@ Usado internamente pelo contexto mt5_conexao().
4
4
  """
5
5
 
6
6
  import MetaTrader5 as mt5
7
+
7
8
  from mtcli.logger import setup_logger
8
9
 
9
10
  log = setup_logger()
@@ -1,101 +1,102 @@
1
- import os
2
- import MetaTrader5 as mt5
3
- from mtcli.conecta import conectar, shutdown
4
- from mtcli.models.conf_model import ConfModel
5
-
6
-
7
- config = ConfModel("mtcli.ini").carregar()
8
-
9
- section = "DEFAULT"
10
- symbol = os.getenv("SYMBOL", config[section].get("symbol", fallback="WIN$N"))
11
- digitos = int(os.getenv("DIGITOS", config[section].getint("digitos", fallback=2)))
12
- PERIOD = period = os.getenv("PERIOD", config[section].get("period", fallback="D1"))
13
- BARS = periodos = count = int(
14
- os.getenv("COUNT", config[section].getint("count", fallback=999))
15
- )
16
- VIEW = view = os.getenv("VIEW", config[section].get("view", fallback="ch"))
17
- VOLUME = volume = os.getenv("VOLUME", config[section].get("volume", fallback="tick"))
18
- DATE = date = os.getenv("DATE", config[section].get("date", fallback=""))
19
-
20
- lateral = os.getenv("LATERAL", config[section].get("lateral", fallback="doji"))
21
- alta = os.getenv("ALTA", config[section].get("alta", fallback="verde"))
22
- baixa = os.getenv("BAIXA", config[section].get("baixa", fallback="vermelho"))
23
- rompimento_alta = os.getenv(
24
- "ROMPIMENTO_ALTA", config[section].get("rompimento_alta", fallback="c")
25
- )
26
- rompimento_baixa = os.getenv(
27
- "ROMPIMENTO_BAIXA", config[section].get("rompimento_baixa", fallback="v")
28
- )
29
- percentual_rompimento = int(
30
- os.getenv(
31
- "PERCENTUAL_ROMPIMENTO",
32
- config[section].getint("percentual_rompimento", fallback=50),
33
- )
34
- )
35
- percentual_doji = int(
36
- os.getenv("PERCENTUAL_DOJI", config[section].getint("percentual_doji", fallback=10))
37
- )
38
- up_bar = os.getenv("UP_BAR", config[section].get("up_bar", fallback="asc"))
39
- down_bar = os.getenv("DOWN_BAR", config[section].get("down_bar", fallback="desc"))
40
- inside_bar = os.getenv("INSIDE_BAR", config[section].get("inside_bar", fallback="ib"))
41
- outside_bar = os.getenv(
42
- "OUTSIDE_BAR", config[section].get("outside_bar", fallback="ob")
43
- )
44
- sombra_superior = os.getenv(
45
- "SOMBRA_SUPERIOR", config[section].get("sombra_superior", fallback="top")
46
- )
47
- sombra_inferior = os.getenv(
48
- "SOMBRA_INFERIOR", config[section].get("sombra_inferior", fallback="bottom")
49
- )
50
- data_source = dados = os.getenv("DADOS", config[section].get("dados", fallback="mt5"))
51
- csv_path = mt5_pasta = os.getenv(
52
- "MT5_PASTA", config[section].get("mt5_pasta", fallback="")
53
- )
54
-
55
-
56
- def get_data_source():
57
- from mtcli.data import CsvDataSource, MT5DataSource
58
-
59
- if data_source.lower() == "csv":
60
- return CsvDataSource()
61
- elif data_source.lower() == "mt5":
62
- return MT5DataSource()
63
- else:
64
- raise ValueError(f"Fonte de dados desconhecida: {data_source}")
65
-
66
-
67
- if not csv_path:
68
- conectar()
69
- terminal_info = mt5.terminal_info()
70
- if terminal_info is None:
71
- raise RuntimeError("Não foi possível obter as informações do terminal.")
72
-
73
- csv_path = terminal_info.data_path + "/MQL5/Files"
74
- shutdown()
75
-
76
- csv_path = csv_path.replace("\\", "/")
77
- csv_path += "/"
78
-
79
- TIMEFRAMES = timeframes = [
80
- "mn1",
81
- "w1",
82
- "d1",
83
- "h12",
84
- "h8",
85
- "h6",
86
- "h4",
87
- "h3",
88
- "h2",
89
- "h1",
90
- "m30",
91
- "m20",
92
- "m15",
93
- "m12",
94
- "m10",
95
- "m6",
96
- "m5",
97
- "m4",
98
- "m3",
99
- "m2",
100
- "m1",
101
- ]
1
+ import os
2
+
3
+ import MetaTrader5 as mt5
4
+
5
+ from mtcli.conecta import conectar, shutdown
6
+ from mtcli.models.conf_model import ConfModel
7
+
8
+ config = ConfModel("mtcli.ini").carregar()
9
+
10
+ section = "DEFAULT"
11
+ symbol = os.getenv("SYMBOL", config[section].get("symbol", fallback="WIN$N"))
12
+ digitos = int(os.getenv("DIGITOS", config[section].getint("digitos", fallback=2)))
13
+ PERIOD = period = os.getenv("PERIOD", config[section].get("period", fallback="D1"))
14
+ BARS = periodos = count = int(
15
+ os.getenv("COUNT", config[section].getint("count", fallback=999))
16
+ )
17
+ VIEW = view = os.getenv("VIEW", config[section].get("view", fallback="ch"))
18
+ VOLUME = volume = os.getenv("VOLUME", config[section].get("volume", fallback="tick"))
19
+ DATE = date = os.getenv("DATE", config[section].get("date", fallback=""))
20
+
21
+ lateral = os.getenv("LATERAL", config[section].get("lateral", fallback="doji"))
22
+ alta = os.getenv("ALTA", config[section].get("alta", fallback="verde"))
23
+ baixa = os.getenv("BAIXA", config[section].get("baixa", fallback="vermelho"))
24
+ rompimento_alta = os.getenv(
25
+ "ROMPIMENTO_ALTA", config[section].get("rompimento_alta", fallback="c")
26
+ )
27
+ rompimento_baixa = os.getenv(
28
+ "ROMPIMENTO_BAIXA", config[section].get("rompimento_baixa", fallback="v")
29
+ )
30
+ percentual_rompimento = int(
31
+ os.getenv(
32
+ "PERCENTUAL_ROMPIMENTO",
33
+ config[section].getint("percentual_rompimento", fallback=50),
34
+ )
35
+ )
36
+ percentual_doji = int(
37
+ os.getenv("PERCENTUAL_DOJI", config[section].getint("percentual_doji", fallback=10))
38
+ )
39
+ up_bar = os.getenv("UP_BAR", config[section].get("up_bar", fallback="asc"))
40
+ down_bar = os.getenv("DOWN_BAR", config[section].get("down_bar", fallback="desc"))
41
+ inside_bar = os.getenv("INSIDE_BAR", config[section].get("inside_bar", fallback="ib"))
42
+ outside_bar = os.getenv(
43
+ "OUTSIDE_BAR", config[section].get("outside_bar", fallback="ob")
44
+ )
45
+ sombra_superior = os.getenv(
46
+ "SOMBRA_SUPERIOR", config[section].get("sombra_superior", fallback="top")
47
+ )
48
+ sombra_inferior = os.getenv(
49
+ "SOMBRA_INFERIOR", config[section].get("sombra_inferior", fallback="bottom")
50
+ )
51
+ data_source = dados = os.getenv("DADOS", config[section].get("dados", fallback="mt5"))
52
+ csv_path = mt5_pasta = os.getenv(
53
+ "MT5_PASTA", config[section].get("mt5_pasta", fallback="")
54
+ )
55
+
56
+
57
+ def get_data_source():
58
+ from mtcli.data import CsvDataSource, MT5DataSource
59
+
60
+ if data_source.lower() == "csv":
61
+ return CsvDataSource()
62
+ elif data_source.lower() == "mt5":
63
+ return MT5DataSource()
64
+ else:
65
+ raise ValueError(f"Fonte de dados desconhecida: {data_source}")
66
+
67
+
68
+ if not csv_path:
69
+ conectar()
70
+ terminal_info = mt5.terminal_info()
71
+ if terminal_info is None:
72
+ raise RuntimeError("Não foi possível obter as informações do terminal.")
73
+
74
+ csv_path = terminal_info.data_path + "/MQL5/Files"
75
+ shutdown()
76
+
77
+ csv_path = csv_path.replace("\\", "/")
78
+ csv_path += "/"
79
+
80
+ TIMEFRAMES = timeframes = [
81
+ "mn1",
82
+ "w1",
83
+ "d1",
84
+ "h12",
85
+ "h8",
86
+ "h6",
87
+ "h4",
88
+ "h3",
89
+ "h2",
90
+ "h1",
91
+ "m30",
92
+ "m20",
93
+ "m15",
94
+ "m12",
95
+ "m10",
96
+ "m6",
97
+ "m5",
98
+ "m4",
99
+ "m3",
100
+ "m2",
101
+ "m1",
102
+ ]
@@ -1,7 +1,5 @@
1
1
  """Módulo fonte de dados via API do MetaTrader 5."""
2
2
 
3
- from datetime import datetime
4
-
5
3
  import MetaTrader5 as mt5
6
4
 
7
5
  from mtcli.logger import setup_logger
@@ -1,45 +1,47 @@
1
- import logging
2
- from logging.handlers import RotatingFileHandler
3
- import os
4
-
5
- LOG_DIR = os.path.join(os.path.expanduser("~"), ".mtcli")
6
- os.makedirs(LOG_DIR, exist_ok=True)
7
- LOG_FILE = os.path.join(LOG_DIR, "mtcli.log")
8
-
9
-
10
- def setup_logger(name: str = "mtcli") -> logging.Logger:
11
- """Configura logger rotativo com saída em arquivo e console.
12
-
13
- - Escreve logs em ~/.mtcli/mtcli.log (máx. 2 MB, 3 backups).
14
- - Mostra logs também no console (stdout), capturáveis via pytest/caplog.
15
- - Evita duplicar handlers.
16
- """
17
-
18
- logger = logging.getLogger(name)
19
- logger.setLevel(logging.DEBUG)
20
-
21
- formatter = logging.Formatter(
22
- "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
23
- datefmt="%Y-%m-%d %H:%M:%S",
24
- )
25
-
26
- # === File handler rotativo ===
27
- if not any(isinstance(h, RotatingFileHandler) for h in logger.handlers):
28
- file_handler = RotatingFileHandler(LOG_FILE, maxBytes=2_000_000, backupCount=3)
29
- file_handler.setFormatter(formatter)
30
- logger.addHandler(file_handler)
31
-
32
- # === Stream handler (console) ===
33
- if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
34
- console_handler = logging.StreamHandler()
35
- console_handler.setFormatter(formatter)
36
- logger.addHandler(console_handler)
37
-
38
- # Permite que pytest caplog capture logs
39
- logger.propagate = True
40
-
41
- return logger
42
-
43
-
44
- # Inicializa logger padrão
45
- log = setup_logger()
1
+ import logging
2
+ from logging.handlers import RotatingFileHandler
3
+ import os
4
+
5
+
6
+ base_dir = os.getenv("APPDATA", os.path.expanduser("~"))
7
+ LOG_DIR = os.path.join(base_dir, "mtcli", "logs")
8
+ os.makedirs(LOG_DIR, exist_ok=True)
9
+ LOG_FILE = os.path.join(LOG_DIR, "mtcli.log")
10
+
11
+
12
+ def setup_logger(name: str = "mtcli") -> logging.Logger:
13
+ """Configura logger rotativo com saída em arquivo e console.
14
+
15
+ - Escreve logs em ~/.mtcli/mtcli.log (máx. 2 MB, 3 backups).
16
+ - Mostra logs também no console (stdout), capturáveis via pytest/caplog.
17
+ - Evita duplicar handlers.
18
+ """
19
+
20
+ logger = logging.getLogger(name)
21
+ logger.setLevel(logging.DEBUG)
22
+
23
+ formatter = logging.Formatter(
24
+ "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
25
+ datefmt="%Y-%m-%d %H:%M:%S",
26
+ )
27
+
28
+ # === File handler rotativo ===
29
+ if not any(isinstance(h, RotatingFileHandler) for h in logger.handlers):
30
+ file_handler = RotatingFileHandler(LOG_FILE, maxBytes=2_000_000, backupCount=3)
31
+ file_handler.setFormatter(formatter)
32
+ logger.addHandler(file_handler)
33
+
34
+ # === Stream handler (console) ===
35
+ if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
36
+ console_handler = logging.StreamHandler()
37
+ console_handler.setFormatter(formatter)
38
+ logger.addHandler(console_handler)
39
+
40
+ # Permite que pytest caplog capture logs
41
+ logger.propagate = True
42
+
43
+ return logger
44
+
45
+
46
+ # Inicializa logger padrão
47
+ log = setup_logger()
@@ -1,8 +1,8 @@
1
1
  """Modelo de configurações."""
2
2
 
3
- import os
4
3
  import configparser
5
4
  from configparser import MissingSectionHeaderError
5
+ import os
6
6
 
7
7
 
8
8
  class ConfigFormatError(Exception):
@@ -10,7 +10,6 @@ class ConfigFormatError(Exception):
10
10
 
11
11
 
12
12
  class ConfModel:
13
-
14
13
  def __init__(self, configuracoes):
15
14
  self.CONFIG_PATH = os.path.abspath(configuracoes)
16
15
 
@@ -1,7 +1,5 @@
1
1
  """Lê os sinais gráficos nas barras."""
2
2
 
3
- from mtcli import conf
4
-
5
3
 
6
4
  class SignalsModel:
7
5
  """Lê sinais das barras."""
@@ -4,8 +4,9 @@ Fornece o contexto 'mt5_conexao' para uso seguro em blocos with.
4
4
  """
5
5
 
6
6
  from contextlib import contextmanager
7
- from mtcli.logger import setup_logger
7
+
8
8
  from mtcli.conecta import conectar, shutdown
9
+ from mtcli.logger import setup_logger
9
10
 
10
11
  log = setup_logger()
11
12
 
@@ -1,25 +1,22 @@
1
- """Modulo da classe model da media movel."""
2
-
3
- from .. import conf
4
-
5
-
6
- class MediaMovelModel:
7
-
8
- def __init__(self, closes, periodos=14):
9
- self.closes = [float(c) for c in closes]
10
- self.periodos = periodos
11
-
12
- def calcula_sma(self):
13
- return [
14
- sum(self.closes[i - self.periodos + 1 : i + 1]) / self.periodos
15
- for i in range(self.periodos - 1, len(self.closes))
16
- ]
17
-
18
- def calcula_ema(self):
19
- ema = []
20
- k = 2 / (self.periodos + 1)
21
- sma = sum(self.closes[: self.periodos]) / self.periodos
22
- ema.append(sma)
23
- for price in self.closes[self.periodos :]:
24
- ema.append(price * k + ema[-1] * (1 - k))
25
- return ema
1
+ """Modulo da classe model da media movel."""
2
+
3
+
4
+ class MediaMovelModel:
5
+ def __init__(self, closes, periodos=14):
6
+ self.closes = [float(c) for c in closes]
7
+ self.periodos = periodos
8
+
9
+ def calcula_sma(self):
10
+ return [
11
+ sum(self.closes[i - self.periodos + 1 : i + 1]) / self.periodos
12
+ for i in range(self.periodos - 1, len(self.closes))
13
+ ]
14
+
15
+ def calcula_ema(self):
16
+ ema = []
17
+ k = 2 / (self.periodos + 1)
18
+ sma = sum(self.closes[: self.periodos]) / self.periodos
19
+ ema.append(sma)
20
+ for price in self.closes[self.periodos :]:
21
+ ema.append(price * k + ema[-1] * (1 - k))
22
+ return ema
@@ -3,6 +3,7 @@
3
3
  import click
4
4
 
5
5
  from mtcli.models.rates_model import RatesModel
6
+
6
7
  from . import conf
7
8
  from .models.average_range_model import AverageRangeModel
8
9
 
@@ -31,7 +31,7 @@ class CloseView:
31
31
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
32
32
  sufixo = f" {data}"
33
33
 
34
- linha = f"{prefixo}" f"{bar.close:.{conf.digitos}f}" f"{sufixo}"
34
+ linha = f"{prefixo}{bar.close:.{conf.digitos}f}{sufixo}"
35
35
  views.append(linha)
36
36
 
37
37
  return views
@@ -31,7 +31,7 @@ class HighView:
31
31
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
32
32
  sufixo = f" {data}"
33
33
 
34
- linha = f"{prefixo}" f"{bar.high:.{conf.digitos}f}" f"{sufixo}"
34
+ linha = f"{prefixo}{bar.high:.{conf.digitos}f}{sufixo}"
35
35
  views.append(linha)
36
36
 
37
37
  return views
@@ -31,7 +31,7 @@ class LowView:
31
31
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
32
32
  sufixo = f" {data}"
33
33
 
34
- linha = f"{prefixo}" f"{bar.low:.{conf.digitos}f}" f"{sufixo}"
34
+ linha = f"{prefixo}{bar.low:.{conf.digitos}f}{sufixo}"
35
35
  views.append(linha)
36
36
 
37
37
  return views
@@ -31,7 +31,7 @@ class OpenView:
31
31
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
32
32
  sufixo = f" {data}"
33
33
 
34
- linha = f"{prefixo}" f"{bar.open:.{conf.digitos}f}" f"{sufixo}"
34
+ linha = f"{prefixo}{bar.open:.{conf.digitos}f}{sufixo}"
35
35
  views.append(linha)
36
36
 
37
37
  return views
@@ -34,9 +34,7 @@ class RangesView:
34
34
  sufixo = f" {data}"
35
35
 
36
36
  linha = (
37
- f"{prefixo}{sequencia} "
38
- f"{bar.trend} {bar.range:.{conf.digitos}f}"
39
- f"{sufixo}"
37
+ f"{prefixo}{sequencia} {bar.trend} {bar.range:.{conf.digitos}f}{sufixo}"
40
38
  )
41
39
  views.append(linha.upper())
42
40
 
@@ -1,6 +1,5 @@
1
1
  """Módulo da classe da view de variações percentuais."""
2
2
 
3
- from mtcli import conf
4
3
  from mtcli.models.chart_model import ChartModel
5
4
 
6
5
 
@@ -41,13 +40,7 @@ class VarsView:
41
40
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
42
41
  sufixo = f" {data}"
43
42
 
44
- linha = (
45
- f"{prefixo}"
46
- f"{var_max:.2f}% "
47
- f"{var_min:.2f}% "
48
- f"{var_fech:.2f}%"
49
- f"{sufixo}"
50
- )
43
+ linha = f"{prefixo}{var_max:.2f}% {var_min:.2f}% {var_fech:.2f}%{sufixo}"
51
44
  views.append(linha.upper())
52
45
 
53
46
  return views
@@ -1,6 +1,5 @@
1
1
  """Módulo da classe da view de volumes."""
2
2
 
3
- from mtcli import conf
4
3
  from mtcli.models.chart_model import ChartModel
5
4
 
6
5
 
@@ -46,7 +45,7 @@ class VolumesView:
46
45
  data = bar.date if self.period in {"d1", "w1", "mn1"} else bar.time
47
46
  sufixo = f" {data}"
48
47
 
49
- linha = f"{prefixo}{sequencia} {sequencia_volume} {volume}" f"{sufixo}"
48
+ linha = f"{prefixo}{sequencia} {sequencia_volume} {volume}{sufixo}"
50
49
  views.append(linha.upper())
51
50
 
52
51
  return views
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.2.1"
3
+ version = "3.3.0.dev0"
4
4
  description = "Aplicativo CLI para exibir gráficos do MetaTrader 5 em texto acessível ao leitor de telas"
5
5
  authors = [
6
6
  {name = "Valmir França da Silva",email = "vfranca3@gmail.com"}
@@ -58,7 +58,32 @@ pymdown-extensions = "^10.16.1"
58
58
  pytest = "^8.4.2"
59
59
  pytest-env = "^1.1.5"
60
60
  pytest-cov = "^6.3.0"
61
- black = "^25.1.0"
62
- isort = "^6.0.1"
63
61
  pydocstyle = "^6.3.0"
62
+ ruff = "^0.14.1"
63
+ pytest-mock = "^3.15.1"
64
64
 
65
+ [tool.ruff]
66
+ line-length = 88
67
+ target-version = "py311"
68
+
69
+ [tool.ruff.lint]
70
+ select = [
71
+ "E", # Estilo
72
+ "F", # Lógica
73
+ "B", # Boas práticas
74
+ "I", # Imports
75
+ "UP", # Upgrade
76
+ "N", # Naming
77
+ ]
78
+ ignore = ["E501"]
79
+
80
+ [tool.ruff.lint.isort]
81
+ known-first-party = ["mtcli"]
82
+ combine-as-imports = true
83
+ force-sort-within-sections = true
84
+
85
+ [tool.ruff.format]
86
+ quote-style = "double"
87
+ indent-style = "space"
88
+ line-ending = "auto"
89
+ skip-magic-trailing-comma = false
@@ -1,4 +0,0 @@
1
- from . import command, conf
2
- from .command import sa
3
- from .models import model_agressao
4
- from .views import view_agressao
@@ -1,45 +0,0 @@
1
- """Exibe o saldo agressor."""
2
-
3
- import click
4
-
5
- from mtcli.models import model_bars, model_rates
6
-
7
- from . import conf
8
- from .models import model_agressao
9
- from .views import view_agressao
10
-
11
-
12
- @click.command()
13
- @click.argument("symbol")
14
- @click.option(
15
- "--period",
16
- "-p",
17
- type=click.Choice(conf.timeframes, case_sensitive=False),
18
- default="d1",
19
- help="Tempo grafico, default D1.",
20
- )
21
- @click.option(
22
- "--count", "-c", type=int, default=20, help="Quantidade de barras, default 20."
23
- )
24
- @click.option(
25
- "--volume",
26
- "-vo",
27
- type=click.Choice(["tick", "real"], case_sensitive=False),
28
- default="tick",
29
- help="Tipo de volume, default tick.",
30
- )
31
- def sa(symbol, period, count, volume):
32
- """Exibe o saldo da agressão."""
33
- period = period.lower()
34
- rates = model_rates.RatesModel(symbol, period)
35
- rates = rates.lista
36
- bars = model_bars.BarsModel(rates)
37
- bars = bars.lista
38
- agressao = model_agressao.AgressaoModel(bars, volume, count)
39
- saldo_agressao = agressao.get_saldo()
40
- view = view_agressao.AgressaoView(saldo_agressao)
41
- click.echo(view.view())
42
-
43
-
44
- if __name__ == "__main__":
45
- sa()
@@ -1 +0,0 @@
1
- from . import model_agressao
@@ -1,39 +0,0 @@
1
- """Módulo da classe model para saldo da agressão."""
2
-
3
- from mtcli import conf
4
-
5
-
6
- class AgressaoModel:
7
- """Classe model do saldo da agressao."""
8
-
9
- def __init__(self, bars, type="tick", count=5):
10
- """Construtor do saldo da agressão."""
11
- self.bars = bars[-count:]
12
- self.type = type
13
- self.count = count
14
- self.volume_comprador = self.__get_volume_comprador()
15
- self.volume_vendedor = self.__get_volume_vendedor()
16
-
17
- def __get_volume_comprador(self):
18
- """Obtem o volume comprador."""
19
- volume = 0
20
- for bar in self.bars:
21
- if (bar.trend == conf.alta and bar.body > 50) or bar.bottom > 50:
22
- volume += bar.volume if self.type == "tick" else bar.volume_real
23
- return volume
24
-
25
- def __get_volume_vendedor(self):
26
- """Obtem o volume vendedor."""
27
- volume = 0
28
- for bar in self.bars:
29
- if (bar.trend == conf.baixa and bar.body > 50) or bar.top > 50:
30
- volume += bar.volume if self.type == "tick" else bar.volume_real
31
- return volume
32
-
33
- def get_saldo(self):
34
- """Calcula o saldo da agressão."""
35
- return (
36
- self.volume_comprador,
37
- self.volume_vendedor,
38
- self.volume_comprador - self.volume_vendedor,
39
- )
@@ -1 +0,0 @@
1
- from . import view_agressao
@@ -1,15 +0,0 @@
1
- """Módulo da classe da view saldo da agressão."""
2
-
3
- from mtcli import conf
4
-
5
-
6
- class AgressaoView:
7
- """Classe da view do saldo da agressao."""
8
-
9
- def __init__(self, saldo_agressao):
10
- """Construtor da view saldo da agressao."""
11
- self.agressao = saldo_agressao
12
-
13
- def view(self):
14
- """View do saldo da agressão."""
15
- return "%i %i %i" % (self.agressao[0], self.agressao[1], self.agressao[2])
@@ -1 +0,0 @@
1
- from mtcli.conf import *
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes