mtcli-volume 2.4.0.dev0__py3-none-any.whl → 2.4.0.dev1__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.
@@ -1,97 +1,102 @@
1
- from datetime import datetime
2
-
3
- import click
4
-
5
- from mtcli_volume.conf import (
6
- FROM,
7
- PERIOD,
8
- PERIODOS,
9
- RANGE,
10
- SYMBOL,
11
- TIMEZONE,
12
- TO,
13
- VOLUME,
14
- )
15
- from mtcli_volume.controllers.volume_controller import calcular_volume_profile
16
- from mtcli_volume.views.volume_view import exibir_volume_profile
17
-
18
-
19
- @click.command(
20
- help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente."
21
- )
22
- @click.version_option(package_name="mtcli-volume")
23
- @click.option(
24
- "--symbol", "-s", default=SYMBOL, show_default=True, help="Símbolo do ativo."
25
- )
26
- @click.option(
27
- "--period",
28
- "-p",
29
- default=PERIOD,
30
- show_default=True,
31
- help="Período do volume.",
32
- )
33
- @click.option(
34
- "--periodos",
35
- "-po",
36
- "bars",
37
- default=PERIODOS,
38
- show_default=True,
39
- help="Quantidade de períodos.",
40
- )
41
- @click.option(
42
- "--range",
43
- "-r",
44
- "step",
45
- type=float,
46
- default=RANGE,
47
- show_default=True,
48
- help="Tamanho do agrupamento de preços.",
49
- )
50
- @click.option(
51
- "--volume",
52
- "-v",
53
- default=VOLUME,
54
- show_default=True,
55
- help="Tipo de volume (tick ou real).",
56
- )
57
- @click.option(
58
- "--from",
59
- "data_inicio",
60
- type=str,
61
- default=FROM,
62
- show_default=True,
63
- help="Data/hora inicial (YYYY-MM-DD HH:MM).",
64
- )
65
- @click.option(
66
- "--to",
67
- "data_fim",
68
- type=str,
69
- default=TO,
70
- show_default=True,
71
- help="Data/hora final (YYYY-MM-DD HH:MM).",
72
- )
73
- @click.option(
74
- "--verbose",
75
- "-vv",
76
- is_flag=True,
77
- help="Mostra informações detalhadas sobre a análise.",
78
- )
79
- @click.option(
80
- "--timezone",
81
- "-tz",
82
- type=str,
83
- default=TIMEZONE,
84
- show_default=True,
85
- help="Fuso horário para exibição das datas (ex: 'UTC', 'America/Sao_Paulo').",
86
- )
87
- def volume(
88
- symbol, period, bars, step, volume, data_inicio, data_fim, verbose, timezone
89
- ):
90
- """Exibe o Volume Profile agrupando volumes por faixa de preço."""
91
- inicio = datetime.strptime(data_inicio, "%Y-%m-%d %H:%M") if data_inicio else None
92
- fim = datetime.strptime(data_fim, "%Y-%m-%d %H:%M") if data_fim else None
93
-
94
- profile, stats, info = calcular_volume_profile(
95
- symbol, period, bars, step, volume, inicio, fim, verbose, timezone
96
- )
97
- exibir_volume_profile(profile, stats, symbol, info, verbose)
1
+ from datetime import datetime
2
+
3
+ import click
4
+
5
+ from .conf import (
6
+ FIM,
7
+ INICIO,
8
+ LIMIT,
9
+ PERIOD,
10
+ RANGE,
11
+ SYMBOL,
12
+ TIMEZONE,
13
+ VOLUME,
14
+ )
15
+ from .controller import calcular_volume_profile
16
+ from .view import exibir_volume_profile
17
+
18
+
19
+ @click.command(
20
+ help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente."
21
+ )
22
+ @click.version_option(package_name="mtcli-volume")
23
+ @click.option(
24
+ "--symbol", "-s", default=SYMBOL, show_default=True, help="Símbolo do ativo."
25
+ )
26
+ @click.option(
27
+ "--period",
28
+ "-p",
29
+ default=PERIOD,
30
+ show_default=True,
31
+ help="Timeframe usado no calculo.",
32
+ )
33
+ @click.option(
34
+ "--limit",
35
+ "-l",
36
+ default=LIMIT,
37
+ show_default=True,
38
+ help="Quantidade de timeframes usados no calculo.",
39
+ )
40
+ @click.option(
41
+ "--range",
42
+ "-r",
43
+ type=float,
44
+ default=RANGE,
45
+ show_default=True,
46
+ help="Tamanho da faixa da distribuicao.",
47
+ )
48
+ @click.option(
49
+ "--volume",
50
+ "-v",
51
+ type=click.Choice(["tick", "real"], case_sensitive=False),
52
+ default=VOLUME,
53
+ show_default=True,
54
+ help="Tipo do volume .",
55
+ )
56
+ @click.option(
57
+ "--format",
58
+ "-fo",
59
+ type=click.Choice(["none", "k", "m", "auto"], case_sensitive=False),
60
+ default="k",
61
+ show_default=True,
62
+ help="Formatacao do volume.",
63
+ )
64
+ @click.option(
65
+ "--inicio",
66
+ "-i",
67
+ default=INICIO,
68
+ show_default=True,
69
+ help="Data/hora inicial (YYYY-MM-DD HH:MM).",
70
+ )
71
+ @click.option(
72
+ "--fim",
73
+ "-f",
74
+ default=FIM,
75
+ show_default=True,
76
+ help="Data/hora final (YYYY-MM-DD HH:MM).",
77
+ )
78
+ @click.option(
79
+ "--verbose",
80
+ "-vv",
81
+ is_flag=True,
82
+ help="Mostra informações detalhadas sobre a análise.",
83
+ )
84
+ @click.option(
85
+ "--timezone",
86
+ "-tz",
87
+ type=str,
88
+ default=TIMEZONE,
89
+ show_default=True,
90
+ help="Fuso horário para exibição das datas (ex: 'UTC', 'America/Sao_Paulo').",
91
+ )
92
+ def volume(
93
+ symbol, period, limit, range, volume, format, inicio, fim, timezone, verbose
94
+ ):
95
+ """Exibe o Volume Profile agrupando volumes por faixa de preço."""
96
+ inicio = datetime.strptime(inicio, "%Y-%m-%d %H:%M") if inicio else None
97
+ fim = datetime.strptime(fim, "%Y-%m-%d %H:%M") if fim else None
98
+
99
+ profile, stats, info = calcular_volume_profile(
100
+ symbol, period, limit, range, volume, inicio, fim, verbose, timezone
101
+ )
102
+ exibir_volume_profile(profile, stats, symbol, info, verbose, format)
mtcli_volume/conf.py CHANGED
@@ -1,17 +1,16 @@
1
- import os
2
-
3
- from mtcli.conf import config
4
-
5
- SYMBOL = os.getenv("SYMBOL", config["DEFAULT"].get("symbol", fallback="WIN$N"))
6
- DIGITOS = int(os.getenv("DIGITOS", config["DEFAULT"].getint("digitos", fallback=0)))
7
- PERIOD = os.getenv("PERIOD", config["DEFAULT"].get("period", fallback="M1"))
8
- PERIODOS = int(
9
- os.getenv("PERIODOS", config["DEFAULT"].getint("periodos", fallback=566))
10
- )
11
- RANGE = float(os.getenv("RANGE", config["DEFAULT"].getfloat("range", fallback=100)))
12
- VOLUME = os.getenv("VOLUME", config["DEFAULT"].get("volume", fallback="tick"))
13
- FROM = os.getenv("FROM", config["DEFAULT"].get("from", fallback=""))
14
- TO = os.getenv("TO", config["DEFAULT"].get("to", fallback=""))
15
- TIMEZONE = os.getenv(
16
- "TIMEZONE", config["DEFAULT"].get("timezone", fallback="America/Sao_Paulo")
17
- )
1
+ import os
2
+
3
+ from mtcli.conf import config
4
+
5
+ SYMBOL = os.getenv("SYMBOL", config["DEFAULT"].get("symbol", fallback="WIN$N"))
6
+ DIGITOS = int(os.getenv("DIGITOS", config["DEFAULT"].getint("digitos", fallback=0)))
7
+ PERIOD = os.getenv("PERIOD", config["DEFAULT"].get("period", fallback="M1"))
8
+ LIMIT = int(os.getenv("LIMIT", config["DEFAULT"].getint("limit", fallback=566)))
9
+ RANGE = float(os.getenv("RANGE", config["DEFAULT"].getfloat("range", fallback=100)))
10
+ VOLUME = os.getenv("VOLUME", config["DEFAULT"].get("volume", fallback="tick"))
11
+ INICIO = os.getenv("INICIO", config["DEFAULT"].get("inicio", fallback=""))
12
+ FIM = os.getenv("FIM", config["DEFAULT"].get("fim", fallback=""))
13
+ TIMEZONE = os.getenv(
14
+ "TIMEZONE", config["DEFAULT"].get("timezone", fallback="America/Sao_Paulo")
15
+ )
16
+ FORMAT = os.getenv("FORMAT", config["DEFAULT"].get("format", fallback="k"))
@@ -1,83 +1,84 @@
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
+
8
+ from .model import (
9
+ calcular_estatisticas,
10
+ calcular_profile,
11
+ obter_rates,
12
+ )
13
+
14
+ log = setup_logger()
15
+
16
+
17
+ def calcular_volume_profile(
18
+ symbol,
19
+ period,
20
+ limit,
21
+ range,
22
+ volume,
23
+ inicio=None,
24
+ fim=None,
25
+ verbose=False,
26
+ timezone_str="America/Sao_Paulo",
27
+ ):
28
+ """Controla o fluxo de cálculo do volume profile, com suporte a timezone configurável."""
29
+ volume = volume.lower().strip()
30
+ if volume not in ["tick", "real"]:
31
+ log.error(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
32
+ raise ValueError(f"Tipo de volume inválido: {volume}. Use 'tick' ou 'real'.")
33
+
34
+ rates = obter_rates(symbol, period, limit, inicio, fim)
35
+
36
+ if rates is None or len(rates) == 0:
37
+ log.error("Falha ao obter dados de preços para cálculo do volume profile.")
38
+ return {}, {}, {}
39
+
40
+ profile = calcular_profile(rates, range, volume)
41
+ stats = calcular_estatisticas(profile)
42
+
43
+ # Captura de informações de contexto
44
+ info = {}
45
+ if isinstance(rates, np.ndarray) and len(rates) > 0:
46
+ primeiro = rates[0]
47
+ ultimo = rates[-1]
48
+ if "time" in rates.dtype.names:
49
+ try:
50
+ # Define fuso horário desejado
51
+ try:
52
+ fuso = zoneinfo.ZoneInfo(timezone_str)
53
+ except Exception:
54
+ log.warning(
55
+ f"Fuso horário '{timezone_str}' inválido. Usando UTC−3 (Brasília)."
56
+ )
57
+ fuso = timezone(timedelta(hours=-3))
58
+
59
+ inicio_real = (
60
+ datetime.utcfromtimestamp(float(primeiro["time"]))
61
+ .astimezone(fuso)
62
+ .strftime("%Y-%m-%d %H:%M:%S")
63
+ )
64
+ fim_real = (
65
+ datetime.utcfromtimestamp(float(ultimo["time"]))
66
+ .astimezone(fuso)
67
+ .strftime("%Y-%m-%d %H:%M:%S")
68
+ )
69
+ except Exception as e:
70
+ log.error(f"Erro ao converter timezone: {e}")
71
+ inicio_real = fim_real = "?"
72
+ else:
73
+ inicio_real = fim_real = "?"
74
+
75
+ info = {
76
+ "symbol": symbol,
77
+ "period": period,
78
+ "candles": len(rates),
79
+ "inicio": inicio_real,
80
+ "fim": fim_real,
81
+ "timezone": timezone_str,
82
+ }
83
+
84
+ return profile, stats, info
mtcli_volume/plugin.py CHANGED
@@ -1,5 +1,5 @@
1
- from mtcli_volume.volume import volume
2
-
3
-
4
- def register(cli):
5
- cli.add_command(volume, name="vp")
1
+ from .cli import volume
2
+
3
+
4
+ def register(cli):
5
+ cli.add_command(volume, name="vp")
@@ -1,11 +1,39 @@
1
1
  import click
2
2
 
3
- from mtcli_volume.conf import DIGITOS
3
+ from .conf import DIGITOS as D
4
4
 
5
5
  BARRA_CHAR = "#"
6
6
 
7
7
 
8
- def exibir_volume_profile(profile, stats, symbol, info=None, verbose=False):
8
+ def formatar_numero(valor: float, modo: str) -> str:
9
+ """
10
+ Formata o volume conforme o modo escolhido:
11
+ - none: valor bruto
12
+ - k: milhares
13
+ - m: milhões
14
+ - auto: decide automaticamente
15
+ """
16
+ modo = (modo or "k").lower()
17
+
18
+ if modo == "none":
19
+ return f"{valor:.{D}f}"
20
+
21
+ if modo == "k":
22
+ return f"{valor / 1000:.{D}f}"
23
+
24
+ if modo == "m":
25
+ return f"{valor / 1_000_000:.{D}f}M"
26
+
27
+ # auto
28
+ if valor >= 1_000_000:
29
+ return f"{valor / 1_000_000:.{D}f}M"
30
+ if valor >= 1000:
31
+ return f"{valor / 1000:.{D}f}K"
32
+
33
+ return f"{valor:.{D}f}"
34
+
35
+
36
+ def exibir_volume_profile(profile, stats, symbol, info=None, verbose=False, format="k"):
9
37
  """Exibe o volume profile no terminal de forma acessível e organizada."""
10
38
  if not profile:
11
39
  click.echo(f"Nenhum dado disponível para {symbol}")
@@ -38,32 +66,34 @@ def exibir_volume_profile(profile, stats, symbol, info=None, verbose=False):
38
66
  click.echo(f"\nVolume Profile — {symbol}\n")
39
67
 
40
68
  max_vol = max(profile.values())
41
- largura_preco = max(len(f"{p:.{DIGITOS}f}") for p in profile.keys())
69
+ largura_preco = max(len(f"{p:.{D}f}") for p in profile.keys())
42
70
 
43
71
  # Cabeçalho
44
- click.echo(f"{'Preço':>{largura_preco}} | Volume 1000X | Distribuição")
45
- click.echo("-" * (largura_preco + 32))
72
+ click.echo(f"{'Preço':>{largura_preco}} | Volume({format.upper()}) | Distribuição")
73
+ click.echo("-" * (largura_preco + 44))
46
74
 
47
75
  # Corpo da tabela
48
76
  for preco, vol in dados_ordenados:
49
77
  barra_len = int(vol / max_vol * 50)
50
78
  barra = BARRA_CHAR * barra_len
51
- click.echo(f"{preco:>{largura_preco}.{DIGITOS}f} | {vol/1000:>6.0f} | {barra}")
79
+ click.echo(
80
+ f"{preco:>{largura_preco}.{D}f} | {formatar_numero(vol, format):>10} | {barra}"
81
+ )
52
82
 
53
83
  # ──────────────────────────────────────────────
54
84
  # BLOCO FINAL: Estatísticas
55
85
  # ──────────────────────────────────────────────
56
86
  click.echo("\n=== Estatísticas ===")
57
87
  if stats.get("poc") is not None:
58
- click.echo(f"POC {stats['poc']:.{DIGITOS}f}")
88
+ click.echo(f"POC {stats['poc']:.{D}f}")
59
89
  click.echo(
60
- f"VA {stats['area_valor'][0]:.{DIGITOS}f} a {stats['area_valor'][1]:.{DIGITOS}f}"
90
+ f"VA {stats['area_valor'][0]:.{D}f} a {stats['area_valor'][1]:.{D}f}"
61
91
  )
62
92
  click.echo(
63
- f"HVNs {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['hvns'])) or 'Nenhum'}"
93
+ f"HVNs {', '.join(map(lambda x: f'{x:.{D}f}', stats['hvns'])) or 'Nenhum'}"
64
94
  )
65
95
  click.echo(
66
- f"LVNs {', '.join(map(lambda x: f'{x:.{DIGITOS}f}', stats['lvns'])) or 'Nenhum'}"
96
+ f"LVNs {', '.join(map(lambda x: f'{x:.{D}f}', stats['lvns'])) or 'Nenhum'}"
67
97
  )
68
98
  else:
69
99
  click.echo("Estatísticas indisponíveis (dados insuficientes).")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mtcli-volume
3
- Version: 2.4.0.dev0
3
+ Version: 2.4.0.dev1
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
@@ -0,0 +1,12 @@
1
+ mtcli_volume/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mtcli_volume/cli.py,sha256=5IpQyl87CNTfxK9y50Oh1lf67lkDm4vWA10q0jXdPr0,2500
3
+ mtcli_volume/conf.py,sha256=U4aucZ260sj6M4d8LOKnqC7T2X00DAnypgaZU93ifjI,866
4
+ mtcli_volume/controller.py,sha256=1Iy96JPc0Sh7y_JPniRzHXZ1ypF1B7sdFhKBignpNHU,2636
5
+ mtcli_volume/model.py,sha256=I8wn3rSQjRlVHaVMFIdZKX-HQpwSVyCgzaYgJK2H7Fk,4221
6
+ mtcli_volume/plugin.py,sha256=EitEOP0FCSf0aSsjHB51tJ0vY3YUucmh3u0avub6oWg,89
7
+ mtcli_volume/view.py,sha256=YySEDxavjcKWN_oPGOFj6Asvvir3dNzlnUJErzdNpDY,3926
8
+ mtcli_volume-2.4.0.dev1.dist-info/entry_points.txt,sha256=JZQvJocNZRSGJF_bOkVJT8JVFERaYC2jCEmm28lMsa0,59
9
+ mtcli_volume-2.4.0.dev1.dist-info/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
10
+ mtcli_volume-2.4.0.dev1.dist-info/METADATA,sha256=ZeccXhrYRSQh536WF-Mx0mk2o4tB5FsLDAA-sJI8OSA,3060
11
+ mtcli_volume-2.4.0.dev1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
12
+ mtcli_volume-2.4.0.dev1.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- mtcli_volume/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mtcli_volume/conf.py,sha256=voyjod5SmIK9O0Xs9HmZbL6gX2qi4dhHTcp1u6-TP0g,780
3
- mtcli_volume/controllers/volume_controller.py,sha256=MpSIyzUaRwU5vM3BqHkJ8fvFcsVP5ZRFoSI5TUJbONI,2593
4
- mtcli_volume/models/volume_model.py,sha256=I8wn3rSQjRlVHaVMFIdZKX-HQpwSVyCgzaYgJK2H7Fk,4221
5
- mtcli_volume/plugin.py,sha256=NQf2buPpM9r0ZGAN9OjXJ6mAu9q26WISvZHIILsd9WU,99
6
- mtcli_volume/views/volume_view.py,sha256=efrI8g5QKz6IyahqUHXsWfsqkamG_VGWKlpDfhhBKYs,3277
7
- mtcli_volume/volume.py,sha256=nEkTfXfy30m5lSq3Qwse2mkLZl4ni8ll8kjCyPvoqtA,2278
8
- mtcli_volume-2.4.0.dev0.dist-info/entry_points.txt,sha256=JZQvJocNZRSGJF_bOkVJT8JVFERaYC2jCEmm28lMsa0,59
9
- mtcli_volume-2.4.0.dev0.dist-info/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
10
- mtcli_volume-2.4.0.dev0.dist-info/METADATA,sha256=pw0TOqRpvqHwkvUI0rPRm13RoMLTEpEP-h1DkS8uWMw,3060
11
- mtcli_volume-2.4.0.dev0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
12
- mtcli_volume-2.4.0.dev0.dist-info/RECORD,,
File without changes