mtcli-volume 2.2.0.dev1__tar.gz → 2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mtcli-volume
3
- Version: 2.2.0.dev1
3
+ Version: 2.3.0.dev0
4
4
  Summary: Plugin mtcli para exibir o volume profile
5
5
  Author: Valmir França da Silva
6
6
  Author-email: vfranca3@gmail.com
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.13
13
13
  Requires-Dist: click (>=8.3.0,<9.0.0)
14
14
  Requires-Dist: metatrader5 (>=5.0.5370,<6.0.0)
15
15
  Requires-Dist: mtcli (>=3.2.0)
16
+ Requires-Dist: tzdata (>=2025.2,<2026.0)
16
17
  Project-URL: Documentation, https://mtcli-volume.readthedocs.io
17
18
  Project-URL: Homepage, https://github.com/vfranca/mtcli-volume
18
19
  Project-URL: Repository, https://github.com/vfranca/mtcli-volume
@@ -8,3 +8,8 @@ PERIOD = os.getenv("PERIOD", config["DEFAULT"].get("period", fallback="M1"))
8
8
  BARS = int(os.getenv("BARS", config["DEFAULT"].getint("bars", fallback=566)))
9
9
  STEP = float(os.getenv("STEP", config["DEFAULT"].getfloat("step", fallback=100)))
10
10
  VOLUME = os.getenv("VOLUME", config["DEFAULT"].get("volume", fallback="tick"))
11
+ FROM = os.getenv("FROM", config["DEFAULT"].get("from", fallback=""))
12
+ TO = os.getenv("TO", config["DEFAULT"].get("to", fallback=""))
13
+ TIMEZONE = os.getenv(
14
+ "TIMEZONE", config["DEFAULT"].get("timezone", fallback="America/Sao_Paulo")
15
+ )
@@ -1,83 +1,83 @@
1
- from datetime import datetime, timedelta, timezone
2
- import zoneinfo # Python 3.9+
3
-
4
- import numpy as np
5
-
6
- from mtcli.logger import setup_logger
7
- from mtcli_volume.models.volume_model import (
8
- calcular_estatisticas,
9
- calcular_profile,
10
- obter_rates,
11
- )
12
-
13
- log = setup_logger()
14
-
15
-
16
- def calcular_volume_profile(
17
- symbol,
18
- period,
19
- bars,
20
- step,
21
- volume,
22
- data_inicio=None,
23
- data_fim=None,
24
- verbose=False,
25
- timezone_str="America/Sao_Paulo",
26
- ):
27
- """Controla o fluxo de cálculo do volume profile, com suporte a timezone configurável."""
28
- volume = volume.lower().strip()
29
- if volume not in ["tick", "real"]:
30
- log.error(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
31
- raise ValueError(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
32
-
33
- rates = obter_rates(symbol, period, bars, data_inicio, data_fim)
34
-
35
- if rates is None or len(rates) == 0:
36
- log.error("Falha ao obter dados de preços para cálculo do volume profile.")
37
- return {}, {}, {}
38
-
39
- profile = calcular_profile(rates, step, volume)
40
- stats = calcular_estatisticas(profile)
41
-
42
- # Captura de informações de contexto
43
- info = {}
44
- if isinstance(rates, np.ndarray) and len(rates) > 0:
45
- primeiro = rates[0]
46
- ultimo = rates[-1]
47
- if "time" in rates.dtype.names:
48
- try:
49
- # Define fuso horário desejado
50
- try:
51
- fuso = zoneinfo.ZoneInfo(timezone_str)
52
- except Exception:
53
- log.warning(
54
- f"Fuso horário '{timezone_str}' inválido. Usando UTC−3 (Brasília)."
55
- )
56
- fuso = timezone(timedelta(hours=-3))
57
-
58
- inicio_real = (
59
- datetime.utcfromtimestamp(float(primeiro["time"]))
60
- .astimezone(fuso)
61
- .strftime("%Y-%m-%d %H:%M:%S")
62
- )
63
- fim_real = (
64
- datetime.utcfromtimestamp(float(ultimo["time"]))
65
- .astimezone(fuso)
66
- .strftime("%Y-%m-%d %H:%M:%S")
67
- )
68
- except Exception as e:
69
- log.error(f"Erro ao converter timezone: {e}")
70
- inicio_real = fim_real = "?"
71
- else:
72
- inicio_real = fim_real = "?"
73
-
74
- info = {
75
- "symbol": symbol,
76
- "period": period,
77
- "candles": len(rates),
78
- "inicio": inicio_real,
79
- "fim": fim_real,
80
- "timezone": timezone_str,
81
- }
82
-
83
- return profile, stats, info
1
+ from datetime import datetime, timedelta, timezone
2
+ import zoneinfo # Python 3.9+
3
+
4
+ import numpy as np
5
+
6
+ from mtcli.logger import setup_logger
7
+ from mtcli_volume.models.volume_model import (
8
+ calcular_estatisticas,
9
+ calcular_profile,
10
+ obter_rates,
11
+ )
12
+
13
+ log = setup_logger()
14
+
15
+
16
+ def calcular_volume_profile(
17
+ symbol,
18
+ period,
19
+ bars,
20
+ step,
21
+ volume,
22
+ data_inicio=None,
23
+ data_fim=None,
24
+ verbose=False,
25
+ timezone_str="America/Sao_Paulo",
26
+ ):
27
+ """Controla o fluxo de cálculo do volume profile, com suporte a timezone configurável."""
28
+ volume = volume.lower().strip()
29
+ if volume not in ["tick", "real"]:
30
+ log.error(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
31
+ raise ValueError(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
32
+
33
+ rates = obter_rates(symbol, period, bars, data_inicio, data_fim)
34
+
35
+ if rates is None or len(rates) == 0:
36
+ log.error("Falha ao obter dados de preços para cálculo do volume profile.")
37
+ return {}, {}, {}
38
+
39
+ profile = calcular_profile(rates, step, volume)
40
+ stats = calcular_estatisticas(profile)
41
+
42
+ # Captura de informações de contexto
43
+ info = {}
44
+ if isinstance(rates, np.ndarray) and len(rates) > 0:
45
+ primeiro = rates[0]
46
+ ultimo = rates[-1]
47
+ if "time" in rates.dtype.names:
48
+ try:
49
+ # Define fuso horário desejado
50
+ try:
51
+ fuso = zoneinfo.ZoneInfo(timezone_str)
52
+ except Exception:
53
+ log.warning(
54
+ f"Fuso horário '{timezone_str}' inválido. Usando UTC−3 (Brasília)."
55
+ )
56
+ fuso = timezone(timedelta(hours=-3))
57
+
58
+ inicio_real = (
59
+ datetime.utcfromtimestamp(float(primeiro["time"]))
60
+ .astimezone(fuso)
61
+ .strftime("%Y-%m-%d %H:%M:%S")
62
+ )
63
+ fim_real = (
64
+ datetime.utcfromtimestamp(float(ultimo["time"]))
65
+ .astimezone(fuso)
66
+ .strftime("%Y-%m-%d %H:%M:%S")
67
+ )
68
+ except Exception as e:
69
+ log.error(f"Erro ao converter timezone: {e}")
70
+ inicio_real = fim_real = "?"
71
+ else:
72
+ inicio_real = fim_real = "?"
73
+
74
+ info = {
75
+ "symbol": symbol,
76
+ "period": period,
77
+ "candles": len(rates),
78
+ "inicio": inicio_real,
79
+ "fim": fim_real,
80
+ "timezone": timezone_str,
81
+ }
82
+
83
+ return profile, stats, info
@@ -1,129 +1,129 @@
1
- from collections import defaultdict
2
- from collections.abc import Mapping
3
- from datetime import datetime
4
-
5
- import MetaTrader5 as mt5
6
- import numpy as np
7
-
8
- from mtcli.logger import setup_logger
9
- from mtcli.mt5_context import mt5_conexao
10
- from mtcli_volume.conf import DIGITOS
11
-
12
- log = setup_logger()
13
-
14
-
15
- def obter_rates(
16
- symbol: str,
17
- period: str,
18
- bars: int,
19
- data_inicio: datetime = None,
20
- data_fim: datetime = None,
21
- ):
22
- """Obtém dados históricos via MetaTrader 5, podendo filtrar por intervalo de tempo."""
23
- with mt5_conexao():
24
- tf = getattr(mt5, f"TIMEFRAME_{period.upper()}", None)
25
- if tf is None:
26
- log.error(f"Timeframe inválido: {period}")
27
- return None
28
-
29
- if not mt5.symbol_select(symbol, True):
30
- log.error(f"Erro ao selecionar símbolo {symbol}")
31
- return None
32
-
33
- try:
34
- if data_inicio and data_fim:
35
- log.debug(
36
- f"Obtendo candles de {symbol} entre {data_inicio} e {data_fim}"
37
- )
38
- rates = mt5.copy_rates_range(symbol, tf, data_inicio, data_fim)
39
- else:
40
- log.debug(f"Obtendo {bars} candles de {symbol} a partir da posição 0")
41
- rates = mt5.copy_rates_from_pos(symbol, tf, 0, bars)
42
- except Exception as e:
43
- log.error(f"Erro ao obter dados históricos: {e}")
44
- return None
45
-
46
- # ✅ Correção para arrays NumPy
47
- if rates is None or len(rates) == 0:
48
- log.error("Nenhum dado retornado.")
49
- return None
50
-
51
- return rates
52
-
53
-
54
- def calcular_profile(
55
- rates: list[dict | tuple | object],
56
- step: float,
57
- volume: str = "tick",
58
- ) -> dict[float, float]:
59
- """Calcula o volume total por faixa de preço, suportando dicionários, numpy.void, objetos ou tuplas."""
60
- profile = defaultdict(int)
61
-
62
- for r in rates:
63
- # --- 1️⃣ Dict comum ---
64
- if isinstance(r, Mapping):
65
- preco = r["close"]
66
- tick_volume = r["tick_volume"]
67
- real_volume = r.get("real_volume", tick_volume)
68
-
69
- # --- 2️⃣ numpy.void (estrutura MT5) ---
70
- elif isinstance(r, np.void):
71
- preco = float(r["close"])
72
- tick_volume = int(r["tick_volume"])
73
- # real_volume pode não existir dependendo da corretora
74
- real_volume = (
75
- int(r["real_volume"]) if "real_volume" in r.dtype.names else tick_volume
76
- )
77
-
78
- # --- 3️⃣ Objeto com atributos (ex: namedtuple) ---
79
- elif hasattr(r, "close"):
80
- preco = r.close
81
- tick_volume = r.tick_volume
82
- real_volume = getattr(r, "real_volume", tick_volume)
83
-
84
- # --- 4️⃣ Tupla/lista ---
85
- elif isinstance(r, (tuple, list)) and len(r) >= 6:
86
- preco = r[4]
87
- tick_volume = r[5]
88
- real_volume = r[6] if len(r) > 6 else tick_volume
89
-
90
- else:
91
- raise TypeError(f"Formato de rate desconhecido: {type(r)}")
92
-
93
- faixa = round(round(preco / step) * step, DIGITOS)
94
- profile[faixa] += tick_volume if volume == "tick" else real_volume
95
-
96
- return dict(profile)
97
-
98
-
99
- def calcular_estatisticas(profile):
100
- """Calcula POC, área de valor (70%), HVNs e LVNs."""
101
- if not profile:
102
- return {"poc": None, "area_valor": (None, None), "hvns": [], "lvns": []}
103
-
104
- # Ordena as faixas de preço por volume (desc)
105
- volumes_ordenados = sorted(profile.items(), key=lambda x: x[1], reverse=True)
106
- poc = volumes_ordenados[0][0]
107
-
108
- # Cálculo da área de valor (70%)
109
- total_volume = sum(profile.values())
110
- acumulado = 0
111
- faixas_area_valor = []
112
- for faixa, vol in volumes_ordenados:
113
- acumulado += vol
114
- faixas_area_valor.append(faixa)
115
- if acumulado >= total_volume * 0.7:
116
- break
117
- area_valor = (min(faixas_area_valor), max(faixas_area_valor))
118
-
119
- # HVNs e LVNs
120
- media = total_volume / len(profile)
121
- hvns = sorted([faixa for faixa, vol in profile.items() if vol >= media * 1.5])
122
- lvns = sorted([faixa for faixa, vol in profile.items() if vol <= media * 0.5])
123
-
124
- return {
125
- "poc": poc,
126
- "area_valor": area_valor,
127
- "hvns": hvns,
128
- "lvns": lvns,
129
- }
1
+ from collections import defaultdict
2
+ from collections.abc import Mapping
3
+ from datetime import datetime
4
+
5
+ import MetaTrader5 as mt5
6
+ import numpy as np
7
+
8
+ from mtcli.logger import setup_logger
9
+ from mtcli.mt5_context import mt5_conexao
10
+ from mtcli_volume.conf import DIGITOS
11
+
12
+ log = setup_logger()
13
+
14
+
15
+ def obter_rates(
16
+ symbol: str,
17
+ period: str,
18
+ bars: int,
19
+ data_inicio: datetime = None,
20
+ data_fim: datetime = None,
21
+ ):
22
+ """Obtém dados históricos via MetaTrader 5, podendo filtrar por intervalo de tempo."""
23
+ with mt5_conexao():
24
+ tf = getattr(mt5, f"TIMEFRAME_{period.upper()}", None)
25
+ if tf is None:
26
+ log.error(f"Timeframe inválido: {period}")
27
+ return None
28
+
29
+ if not mt5.symbol_select(symbol, True):
30
+ log.error(f"Erro ao selecionar símbolo {symbol}")
31
+ return None
32
+
33
+ try:
34
+ if data_inicio and data_fim:
35
+ log.debug(
36
+ f"Obtendo candles de {symbol} entre {data_inicio} e {data_fim}"
37
+ )
38
+ rates = mt5.copy_rates_range(symbol, tf, data_inicio, data_fim)
39
+ else:
40
+ log.debug(f"Obtendo {bars} candles de {symbol} a partir da posição 0")
41
+ rates = mt5.copy_rates_from_pos(symbol, tf, 0, bars)
42
+ except Exception as e:
43
+ log.error(f"Erro ao obter dados históricos: {e}")
44
+ return None
45
+
46
+ # ✅ Correção para arrays NumPy
47
+ if rates is None or len(rates) == 0:
48
+ log.error("Nenhum dado retornado.")
49
+ return None
50
+
51
+ return rates
52
+
53
+
54
+ def calcular_profile(
55
+ rates: list[dict | tuple | object],
56
+ step: float,
57
+ volume: str = "tick",
58
+ ) -> dict[float, float]:
59
+ """Calcula o volume total por faixa de preço, suportando dicionários, numpy.void, objetos ou tuplas."""
60
+ profile = defaultdict(int)
61
+
62
+ for r in rates:
63
+ # --- 1️⃣ Dict comum ---
64
+ if isinstance(r, Mapping):
65
+ preco = r["close"]
66
+ tick_volume = r["tick_volume"]
67
+ real_volume = r.get("real_volume", tick_volume)
68
+
69
+ # --- 2️⃣ numpy.void (estrutura MT5) ---
70
+ elif isinstance(r, np.void):
71
+ preco = float(r["close"])
72
+ tick_volume = int(r["tick_volume"])
73
+ # real_volume pode não existir dependendo da corretora
74
+ real_volume = (
75
+ int(r["real_volume"]) if "real_volume" in r.dtype.names else tick_volume
76
+ )
77
+
78
+ # --- 3️⃣ Objeto com atributos (ex: namedtuple) ---
79
+ elif hasattr(r, "close"):
80
+ preco = r.close
81
+ tick_volume = r.tick_volume
82
+ real_volume = getattr(r, "real_volume", tick_volume)
83
+
84
+ # --- 4️⃣ Tupla/lista ---
85
+ elif isinstance(r, (tuple, list)) and len(r) >= 6:
86
+ preco = r[4]
87
+ tick_volume = r[5]
88
+ real_volume = r[6] if len(r) > 6 else tick_volume
89
+
90
+ else:
91
+ raise TypeError(f"Formato de rate desconhecido: {type(r)}")
92
+
93
+ faixa = round(round(preco / step) * step, DIGITOS)
94
+ profile[faixa] += tick_volume if volume == "tick" else real_volume
95
+
96
+ return dict(profile)
97
+
98
+
99
+ def calcular_estatisticas(profile):
100
+ """Calcula POC, área de valor (70%), HVNs e LVNs."""
101
+ if not profile:
102
+ return {"poc": None, "area_valor": (None, None), "hvns": [], "lvns": []}
103
+
104
+ # Ordena as faixas de preço por volume (desc)
105
+ volumes_ordenados = sorted(profile.items(), key=lambda x: x[1], reverse=True)
106
+ poc = volumes_ordenados[0][0]
107
+
108
+ # Cálculo da área de valor (70%)
109
+ total_volume = sum(profile.values())
110
+ acumulado = 0
111
+ faixas_area_valor = []
112
+ for faixa, vol in volumes_ordenados:
113
+ acumulado += vol
114
+ faixas_area_valor.append(faixa)
115
+ if acumulado >= total_volume * 0.7:
116
+ break
117
+ area_valor = (min(faixas_area_valor), max(faixas_area_valor))
118
+
119
+ # HVNs e LVNs
120
+ media = total_volume / len(profile)
121
+ hvns = sorted([faixa for faixa, vol in profile.items() if vol >= media * 1.5])
122
+ lvns = sorted([faixa for faixa, vol in profile.items() if vol <= media * 0.5])
123
+
124
+ return {
125
+ "poc": poc,
126
+ "area_valor": area_valor,
127
+ "hvns": hvns,
128
+ "lvns": lvns,
129
+ }
@@ -2,4 +2,4 @@ from mtcli_volume.volume import volume
2
2
 
3
3
 
4
4
  def register(cli):
5
- cli.add_command(volume, name="volume")
5
+ cli.add_command(volume, name="vp")
@@ -1,69 +1,69 @@
1
- import click
2
-
3
- from mtcli_volume.conf import DIGITOS
4
-
5
- BARRA_CHAR = "#"
6
-
7
-
8
- def exibir_volume_profile(profile, stats, symbol, info=None, verbose=False):
9
- """Exibe o volume profile no terminal de forma acessível e organizada."""
10
- if not profile:
11
- click.echo(f"Nenhum dado disponível para {symbol}")
12
- return
13
-
14
- # ──────────────────────────────────────────────
15
- # BLOCO VERBOSO: exibe detalhes da análise
16
- # ──────────────────────────────────────────────
17
- if verbose and info:
18
- click.echo("\n=== Informações da Análise ===")
19
- linhas = [
20
- ("Símbolo", info.get("symbol", "?")),
21
- ("Timeframe", info.get("period", "?").upper()),
22
- ("Candles analisados", str(info.get("candles", "?"))),
23
- (
24
- "Período analisado",
25
- f"{info.get('inicio', '?')} → {info.get('fim', '?')}",
26
- ),
27
- ("Fuso horário", info.get("timezone", "Desconhecido")),
28
- ]
29
- largura_esq = max(len(t[0]) for t in linhas) + 2
30
- for chave, valor in linhas:
31
- click.echo(f"{chave:<{largura_esq}}: {valor}")
32
- click.echo("=" * (largura_esq + 30))
33
-
34
- # ──────────────────────────────────────────────
35
- # BLOCO PRINCIPAL: Volume Profile
36
- # ──────────────────────────────────────────────
37
- dados_ordenados = sorted(profile.items(), reverse=True)
38
- click.echo(f"\n📊 Volume Profile — {symbol}\n")
39
-
40
- max_vol = max(profile.values())
41
- largura_preco = max(len(f"{p:.{DIGITOS}f}") for p in profile.keys())
42
-
43
- # Cabeçalho
44
- click.echo(f"{'Preço':>{largura_preco}} | Volume | Distribuição")
45
- click.echo("-" * (largura_preco + 32))
46
-
47
- # Corpo da tabela
48
- for preco, vol in dados_ordenados:
49
- barra_len = int(vol / max_vol * 50)
50
- barra = BARRA_CHAR * barra_len
51
- click.echo(f"{preco:>{largura_preco}.{DIGITOS}f} | {vol:>6} | {barra}")
52
-
53
- # ──────────────────────────────────────────────
54
- # BLOCO FINAL: Estatísticas
55
- # ──────────────────────────────────────────────
56
- click.echo("\n=== Estatísticas ===")
57
- if stats.get("poc") is not None:
58
- click.echo(f"POC : {stats['poc']:.{DIGITOS}f}")
59
- click.echo(
60
- f"Área de Valor : {stats['area_valor'][0]:.{DIGITOS}f} {stats['area_valor'][1]:.{DIGITOS}f}"
61
- )
62
- click.echo(
63
- f"HVNs (High Vol.) : {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['hvns'])) or 'Nenhum'}"
64
- )
65
- click.echo(
66
- f"LVNs (Low Vol.) : {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['lvns'])) or 'Nenhum'}"
67
- )
68
- else:
69
- click.echo("Estatísticas indisponíveis (dados insuficientes).")
1
+ import click
2
+
3
+ from mtcli_volume.conf import DIGITOS
4
+
5
+ BARRA_CHAR = "#"
6
+
7
+
8
+ def exibir_volume_profile(profile, stats, symbol, info=None, verbose=False):
9
+ """Exibe o volume profile no terminal de forma acessível e organizada."""
10
+ if not profile:
11
+ click.echo(f"Nenhum dado disponível para {symbol}")
12
+ return
13
+
14
+ # ──────────────────────────────────────────────
15
+ # BLOCO VERBOSO: exibe detalhes da análise
16
+ # ──────────────────────────────────────────────
17
+ if verbose and info:
18
+ click.echo("\n=== Informações da Análise ===")
19
+ linhas = [
20
+ ("Símbolo", info.get("symbol", "?")),
21
+ ("Timeframe", info.get("period", "?").upper()),
22
+ ("Candles analisados", str(info.get("candles", "?"))),
23
+ (
24
+ "Período analisado",
25
+ f"{info.get('inicio', '?')} → {info.get('fim', '?')}",
26
+ ),
27
+ ("Fuso horário", info.get("timezone", "Desconhecido")),
28
+ ]
29
+ largura_esq = max(len(t[0]) for t in linhas) + 2
30
+ for chave, valor in linhas:
31
+ click.echo(f"{chave:<{largura_esq}}: {valor}")
32
+ click.echo("=" * (largura_esq + 30))
33
+
34
+ # ──────────────────────────────────────────────
35
+ # BLOCO PRINCIPAL: Volume Profile
36
+ # ──────────────────────────────────────────────
37
+ dados_ordenados = sorted(profile.items(), reverse=True)
38
+ click.echo(f"\nVolume Profile — {symbol}\n")
39
+
40
+ max_vol = max(profile.values())
41
+ largura_preco = max(len(f"{p:.{DIGITOS}f}") for p in profile.keys())
42
+
43
+ # Cabeçalho
44
+ click.echo(f"{'Preço':>{largura_preco}} | Volume | Distribuição")
45
+ click.echo("-" * (largura_preco + 32))
46
+
47
+ # Corpo da tabela
48
+ for preco, vol in dados_ordenados:
49
+ barra_len = int(vol / max_vol * 50)
50
+ barra = BARRA_CHAR * barra_len
51
+ click.echo(f"{preco:>{largura_preco}.{DIGITOS}f} | {vol:>6} | {barra}")
52
+
53
+ # ──────────────────────────────────────────────
54
+ # BLOCO FINAL: Estatísticas
55
+ # ──────────────────────────────────────────────
56
+ click.echo("\n=== Estatísticas ===")
57
+ if stats.get("poc") is not None:
58
+ click.echo(f"POC {stats['poc']:.{DIGITOS}f}")
59
+ click.echo(
60
+ f"VA {stats['area_valor'][0]:.{DIGITOS}f} a {stats['area_valor'][1]:.{DIGITOS}f}"
61
+ )
62
+ click.echo(
63
+ f"HVNs {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['hvns'])) or 'Nenhum'}"
64
+ )
65
+ click.echo(
66
+ f"LVNs {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['lvns'])) or 'Nenhum'}"
67
+ )
68
+ else:
69
+ click.echo("Estatísticas indisponíveis (dados insuficientes).")
@@ -2,14 +2,13 @@ from datetime import datetime
2
2
 
3
3
  import click
4
4
 
5
- from mtcli_volume.conf import BARS, PERIOD, STEP, SYMBOL, VOLUME
5
+ from mtcli_volume.conf import BARS, FROM, PERIOD, STEP, SYMBOL, TIMEZONE, TO, VOLUME
6
6
  from mtcli_volume.controllers.volume_controller import calcular_volume_profile
7
7
  from mtcli_volume.views.volume_view import exibir_volume_profile
8
8
 
9
9
 
10
10
  @click.command(
11
- "volume",
12
- help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente.",
11
+ help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente."
13
12
  )
14
13
  @click.version_option(package_name="mtcli-volume")
15
14
  @click.option(
@@ -20,12 +19,20 @@ from mtcli_volume.views.volume_view import exibir_volume_profile
20
19
  "-p",
21
20
  default=PERIOD,
22
21
  show_default=True,
23
- help="Timeframe (ex: M1, M5, H1).",
22
+ help="Período do volume.",
24
23
  )
25
- @click.option("--bars", "-b", default=BARS, show_default=True, help="Número de barras.")
26
24
  @click.option(
27
- "--step",
28
- "-e",
25
+ "--periodos",
26
+ "-po",
27
+ "bars",
28
+ default=BARS,
29
+ show_default=True,
30
+ help="Quantidade de períodos.",
31
+ )
32
+ @click.option(
33
+ "--range",
34
+ "-r",
35
+ "step",
29
36
  type=float,
30
37
  default=STEP,
31
38
  show_default=True,
@@ -39,9 +46,21 @@ from mtcli_volume.views.volume_view import exibir_volume_profile
39
46
  help="Tipo de volume (tick ou real).",
40
47
  )
41
48
  @click.option(
42
- "--from", "data_inicio", type=str, help="Data/hora inicial (YYYY-MM-DD HH:MM)."
49
+ "--from",
50
+ "data_inicio",
51
+ type=str,
52
+ default=FROM,
53
+ show_default=True,
54
+ help="Data/hora inicial (YYYY-MM-DD HH:MM).",
55
+ )
56
+ @click.option(
57
+ "--to",
58
+ "data_fim",
59
+ type=str,
60
+ default=TO,
61
+ show_default=True,
62
+ help="Data/hora final (YYYY-MM-DD HH:MM).",
43
63
  )
44
- @click.option("--to", "data_fim", type=str, help="Data/hora final (YYYY-MM-DD HH:MM).")
45
64
  @click.option(
46
65
  "--verbose",
47
66
  "-vv",
@@ -52,7 +71,7 @@ from mtcli_volume.views.volume_view import exibir_volume_profile
52
71
  "--timezone",
53
72
  "-tz",
54
73
  type=str,
55
- default="America/Sao_Paulo",
74
+ default=TIMEZONE,
56
75
  show_default=True,
57
76
  help="Fuso horário para exibição das datas (ex: 'UTC', 'America/Sao_Paulo').",
58
77
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli-volume"
3
- version = "2.2.0.dev1"
3
+ version = "2.3.0.dev0"
4
4
  description = "Plugin mtcli para exibir o volume profile"
5
5
  authors = [
6
6
  {name = "Valmir França da Silva",email = "vfranca3@gmail.com"}
@@ -10,7 +10,8 @@ requires-python = ">=3.10,<3.14.0"
10
10
  dependencies = [
11
11
  "mtcli>=3.2.0",
12
12
  "click (>=8.3.0,<9.0.0)",
13
- "metatrader5 (>=5.0.5370,<6.0.0)"
13
+ "metatrader5 (>=5.0.5370,<6.0.0)",
14
+ "tzdata (>=2025.2,<2026.0)"
14
15
  ]
15
16
 
16
17
  [project.urls]