mtcli-volume 2.0.0.dev2__tar.gz → 2.1.0__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.
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/PKG-INFO +3 -1
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/commands/volume_cli.py +46 -38
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/conf.py +10 -10
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/models/volume_model.py +98 -98
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/plugin.py +5 -5
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/views/volume_view.py +4 -6
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/pyproject.toml +4 -2
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/LICENSE +0 -0
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/README.md +0 -0
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/__init__.py +0 -0
- {mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/controllers/volume_controller.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mtcli-volume
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
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
|
|
@@ -10,6 +10,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: click (>=8.3.0,<9.0.0)
|
|
14
|
+
Requires-Dist: metatrader5 (>=5.0.5370,<6.0.0)
|
|
13
15
|
Requires-Dist: mtcli (>=3.2.0)
|
|
14
16
|
Project-URL: Documentation, https://mtcli-volume.readthedocs.io
|
|
15
17
|
Project-URL: Homepage, https://github.com/vfranca/mtcli-volume
|
|
@@ -1,38 +1,46 @@
|
|
|
1
|
-
import click
|
|
2
|
-
|
|
3
|
-
from mtcli_volume.conf import BARS, PERIOD, STEP, SYMBOL, VOLUME
|
|
4
|
-
from mtcli_volume.controllers.volume_controller import calcular_volume_profile
|
|
5
|
-
from mtcli_volume.views.volume_view import exibir_volume_profile
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@click.command(
|
|
9
|
-
"volume",
|
|
10
|
-
help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente.",
|
|
11
|
-
)
|
|
12
|
-
@click.version_option(package_name="mtcli-volume")
|
|
13
|
-
@click.option(
|
|
14
|
-
"--symbol", "-s", default=SYMBOL, help="
|
|
15
|
-
)
|
|
16
|
-
@click.option(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
default=
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mtcli_volume.conf import BARS, PERIOD, STEP, SYMBOL, VOLUME
|
|
4
|
+
from mtcli_volume.controllers.volume_controller import calcular_volume_profile
|
|
5
|
+
from mtcli_volume.views.volume_view import exibir_volume_profile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command(
|
|
9
|
+
"volume",
|
|
10
|
+
help="Exibe o Volume Profile, agrupando volumes por faixa de preço no histórico recente.",
|
|
11
|
+
)
|
|
12
|
+
@click.version_option(package_name="mtcli-volume")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--symbol", "-s", default=SYMBOL, show_default=True, help="Simbolo do ativo."
|
|
15
|
+
)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--period",
|
|
18
|
+
"-p",
|
|
19
|
+
default=PERIOD,
|
|
20
|
+
show_default=True,
|
|
21
|
+
help="Timeframe (ex: M1, M5, H1).",
|
|
22
|
+
)
|
|
23
|
+
@click.option("--bars", "-b", default=BARS, show_default=True, help="Numero de barras.")
|
|
24
|
+
@click.option(
|
|
25
|
+
"--step",
|
|
26
|
+
"-e",
|
|
27
|
+
type=float,
|
|
28
|
+
default=STEP,
|
|
29
|
+
show_default=True,
|
|
30
|
+
help="Tamanho do agrupamento de precos.",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--volume",
|
|
34
|
+
"-v",
|
|
35
|
+
default=VOLUME,
|
|
36
|
+
show_default=True,
|
|
37
|
+
help="Tipo de volume (tick ou real).",
|
|
38
|
+
)
|
|
39
|
+
def volume(symbol, period, bars, step, volume):
|
|
40
|
+
"""Exibe o Volume Profile agrupando volumes por faixa de preço."""
|
|
41
|
+
profile, stats = calcular_volume_profile(symbol, period, bars, step, volume)
|
|
42
|
+
exibir_volume_profile(profile, stats, symbol)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
volume()
|
|
@@ -1,10 +1,10 @@
|
|
|
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
|
-
BARS = int(os.getenv("BARS", config["DEFAULT"].getint("bars", fallback=566)))
|
|
9
|
-
STEP = float(os.getenv("STEP", config["DEFAULT"].getfloat("step", fallback=100)))
|
|
10
|
-
VOLUME = os.getenv("VOLUME", config["DEFAULT"].get("volume", fallback="tick"))
|
|
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
|
+
BARS = int(os.getenv("BARS", config["DEFAULT"].getint("bars", fallback=566)))
|
|
9
|
+
STEP = float(os.getenv("STEP", config["DEFAULT"].getfloat("step", fallback=100)))
|
|
10
|
+
VOLUME = os.getenv("VOLUME", config["DEFAULT"].get("volume", fallback="tick"))
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
|
-
|
|
3
|
-
import MetaTrader5 as mt5
|
|
4
|
-
|
|
5
|
-
from mtcli.logger import setup_logger
|
|
6
|
-
from mtcli.models.rates_model import RatesModel
|
|
7
|
-
from mtcli.mt5_context import mt5_conexao
|
|
8
|
-
from mtcli_volume.conf import DIGITOS
|
|
9
|
-
|
|
10
|
-
log = setup_logger()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def obter_rates(symbol, period, bars):
|
|
14
|
-
"""Obtém os dados históricos de preços via MetaTrader 5."""
|
|
15
|
-
with mt5_conexao():
|
|
16
|
-
tf = getattr(mt5, f"TIMEFRAME_{period.upper()}", None)
|
|
17
|
-
if tf is None:
|
|
18
|
-
log.error(f"Timeframe inválido: {period}")
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
if not mt5.symbol_select(symbol, True):
|
|
22
|
-
log.error(f"Erro ao selecionar símbolo {symbol}")
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
rates = RatesModel(symbol, period, bars).get_data()
|
|
26
|
-
|
|
27
|
-
if not rates:
|
|
28
|
-
log.error("Erro: não foi possível obter os dados históricos.")
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
return rates
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def calcular_profile(rates, step, volume="tick"):
|
|
35
|
-
"""Calcula o volume total por faixa de preço, suportando dicionários, objetos ou tuplas."""
|
|
36
|
-
from collections.abc import Mapping
|
|
37
|
-
|
|
38
|
-
profile = defaultdict(int)
|
|
39
|
-
|
|
40
|
-
for r in rates:
|
|
41
|
-
# Detecta se r é dict, objeto com atributos ou tupla
|
|
42
|
-
if isinstance(r, Mapping): # dict
|
|
43
|
-
preco = r["close"]
|
|
44
|
-
tick_volume = r["tick_volume"]
|
|
45
|
-
real_volume = r.get("real_volume", tick_volume)
|
|
46
|
-
elif hasattr(r, "close"): # objeto com atributos
|
|
47
|
-
preco = r.close
|
|
48
|
-
tick_volume = r.tick_volume
|
|
49
|
-
real_volume = getattr(r, "real_volume", tick_volume)
|
|
50
|
-
elif isinstance(r, (tuple, list)) and len(r) >= 6: # tupla com campos esperados
|
|
51
|
-
preco = r[4] # índice típico de 'close' em rates do MT5
|
|
52
|
-
tick_volume = r[5]
|
|
53
|
-
real_volume = r[6] if len(r) > 6 else tick_volume
|
|
54
|
-
else:
|
|
55
|
-
raise TypeError(f"Formato de rate desconhecido: {type(r)}")
|
|
56
|
-
|
|
57
|
-
faixa = round(round(preco / step) * step, DIGITOS)
|
|
58
|
-
profile[faixa] += tick_volume if volume == "tick" else real_volume
|
|
59
|
-
|
|
60
|
-
return dict(profile)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def calcular_estatisticas(profile):
|
|
64
|
-
"""Calcula POC, área de valor (70%), HVNs e LVNs."""
|
|
65
|
-
if profile is None or len(profile) == 0:
|
|
66
|
-
return {
|
|
67
|
-
"poc": None,
|
|
68
|
-
"area_valor": (None, None),
|
|
69
|
-
"hvns": [],
|
|
70
|
-
"lvns": [],
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
# Ordena faixas de preço pelo volume em ordem decrescente
|
|
74
|
-
volumes_ordenados = sorted(profile.items(), key=lambda x: x[1], reverse=True)
|
|
75
|
-
poc = volumes_ordenados[0][0]
|
|
76
|
-
|
|
77
|
-
# Área de valor (70% do volume)
|
|
78
|
-
total_volume = sum(profile.values())
|
|
79
|
-
acumulado = 0
|
|
80
|
-
faixas_area_valor = []
|
|
81
|
-
for faixa, vol in volumes_ordenados:
|
|
82
|
-
acumulado += vol
|
|
83
|
-
faixas_area_valor.append(faixa)
|
|
84
|
-
if acumulado >= total_volume * 0.7:
|
|
85
|
-
break
|
|
86
|
-
area_valor = (min(faixas_area_valor), max(faixas_area_valor))
|
|
87
|
-
|
|
88
|
-
# HVNs (High Volume Nodes) e LVNs (Low Volume Nodes)
|
|
89
|
-
media = total_volume / len(profile)
|
|
90
|
-
hvns = sorted([faixa for faixa, vol in profile.items() if vol >= media * 1.5])
|
|
91
|
-
lvns = sorted([faixa for faixa, vol in profile.items() if vol <= media * 0.5])
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
"poc": poc,
|
|
95
|
-
"area_valor": area_valor,
|
|
96
|
-
"hvns": hvns,
|
|
97
|
-
"lvns": lvns,
|
|
98
|
-
}
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
import MetaTrader5 as mt5
|
|
4
|
+
|
|
5
|
+
from mtcli.logger import setup_logger
|
|
6
|
+
from mtcli.models.rates_model import RatesModel
|
|
7
|
+
from mtcli.mt5_context import mt5_conexao
|
|
8
|
+
from mtcli_volume.conf import DIGITOS
|
|
9
|
+
|
|
10
|
+
log = setup_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def obter_rates(symbol, period, bars):
|
|
14
|
+
"""Obtém os dados históricos de preços via MetaTrader 5."""
|
|
15
|
+
with mt5_conexao():
|
|
16
|
+
tf = getattr(mt5, f"TIMEFRAME_{period.upper()}", None)
|
|
17
|
+
if tf is None:
|
|
18
|
+
log.error(f"Timeframe inválido: {period}")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
if not mt5.symbol_select(symbol, True):
|
|
22
|
+
log.error(f"Erro ao selecionar símbolo {symbol}")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
rates = RatesModel(symbol, period, bars).get_data()
|
|
26
|
+
|
|
27
|
+
if not rates:
|
|
28
|
+
log.error("Erro: não foi possível obter os dados históricos.")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
return rates
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def calcular_profile(rates, step, volume="tick"):
|
|
35
|
+
"""Calcula o volume total por faixa de preço, suportando dicionários, objetos ou tuplas."""
|
|
36
|
+
from collections.abc import Mapping
|
|
37
|
+
|
|
38
|
+
profile = defaultdict(int)
|
|
39
|
+
|
|
40
|
+
for r in rates:
|
|
41
|
+
# Detecta se r é dict, objeto com atributos ou tupla
|
|
42
|
+
if isinstance(r, Mapping): # dict
|
|
43
|
+
preco = r["close"]
|
|
44
|
+
tick_volume = r["tick_volume"]
|
|
45
|
+
real_volume = r.get("real_volume", tick_volume)
|
|
46
|
+
elif hasattr(r, "close"): # objeto com atributos
|
|
47
|
+
preco = r.close
|
|
48
|
+
tick_volume = r.tick_volume
|
|
49
|
+
real_volume = getattr(r, "real_volume", tick_volume)
|
|
50
|
+
elif isinstance(r, (tuple, list)) and len(r) >= 6: # tupla com campos esperados
|
|
51
|
+
preco = r[4] # índice típico de 'close' em rates do MT5
|
|
52
|
+
tick_volume = r[5]
|
|
53
|
+
real_volume = r[6] if len(r) > 6 else tick_volume
|
|
54
|
+
else:
|
|
55
|
+
raise TypeError(f"Formato de rate desconhecido: {type(r)}")
|
|
56
|
+
|
|
57
|
+
faixa = round(round(preco / step) * step, DIGITOS)
|
|
58
|
+
profile[faixa] += tick_volume if volume == "tick" else real_volume
|
|
59
|
+
|
|
60
|
+
return dict(profile)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def calcular_estatisticas(profile):
|
|
64
|
+
"""Calcula POC, área de valor (70%), HVNs e LVNs."""
|
|
65
|
+
if profile is None or len(profile) == 0:
|
|
66
|
+
return {
|
|
67
|
+
"poc": None,
|
|
68
|
+
"area_valor": (None, None),
|
|
69
|
+
"hvns": [],
|
|
70
|
+
"lvns": [],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Ordena faixas de preço pelo volume em ordem decrescente
|
|
74
|
+
volumes_ordenados = sorted(profile.items(), key=lambda x: x[1], reverse=True)
|
|
75
|
+
poc = volumes_ordenados[0][0]
|
|
76
|
+
|
|
77
|
+
# Área de valor (70% do volume)
|
|
78
|
+
total_volume = sum(profile.values())
|
|
79
|
+
acumulado = 0
|
|
80
|
+
faixas_area_valor = []
|
|
81
|
+
for faixa, vol in volumes_ordenados:
|
|
82
|
+
acumulado += vol
|
|
83
|
+
faixas_area_valor.append(faixa)
|
|
84
|
+
if acumulado >= total_volume * 0.7:
|
|
85
|
+
break
|
|
86
|
+
area_valor = (min(faixas_area_valor), max(faixas_area_valor))
|
|
87
|
+
|
|
88
|
+
# HVNs (High Volume Nodes) e LVNs (Low Volume Nodes)
|
|
89
|
+
media = total_volume / len(profile)
|
|
90
|
+
hvns = sorted([faixa for faixa, vol in profile.items() if vol >= media * 1.5])
|
|
91
|
+
lvns = sorted([faixa for faixa, vol in profile.items() if vol <= media * 0.5])
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"poc": poc,
|
|
95
|
+
"area_valor": area_valor,
|
|
96
|
+
"hvns": hvns,
|
|
97
|
+
"lvns": lvns,
|
|
98
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from mtcli_volume.commands.volume_cli import volume
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def register(cli):
|
|
5
|
-
cli.add_command(volume, name="volume")
|
|
1
|
+
from mtcli_volume.commands.volume_cli import volume
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def register(cli):
|
|
5
|
+
cli.add_command(volume, name="volume")
|
|
@@ -21,11 +21,9 @@ def exibir_volume_profile(profile, stats, symbol):
|
|
|
21
21
|
|
|
22
22
|
# Estatísticas
|
|
23
23
|
if stats.get("poc") is not None:
|
|
24
|
-
click.echo(f"\nPOC
|
|
25
|
-
click.echo(
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
click.echo(f"HVNs: {stats['hvns']}")
|
|
29
|
-
click.echo(f"LVNs: {stats['lvns']}")
|
|
24
|
+
click.echo(f"\nPOC {stats['poc']:.{DIGITOS}f}")
|
|
25
|
+
click.echo(f"VA {stats['area_valor'][0]:.{DIGITOS}f} a {stats['area_valor'][1]:.{DIGITOS}f}")
|
|
26
|
+
click.echo(f"HVNs {stats['hvns']}")
|
|
27
|
+
click.echo(f"LVNs {stats['lvns']}")
|
|
30
28
|
else:
|
|
31
29
|
click.echo("\nEstatísticas indisponíveis (dados insuficientes).")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mtcli-volume"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.1.0"
|
|
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"}
|
|
@@ -8,7 +8,9 @@ authors = [
|
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.10,<3.14.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"mtcli>=3.2.0"
|
|
11
|
+
"mtcli>=3.2.0",
|
|
12
|
+
"click (>=8.3.0,<9.0.0)",
|
|
13
|
+
"metatrader5 (>=5.0.5370,<6.0.0)"
|
|
12
14
|
]
|
|
13
15
|
|
|
14
16
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mtcli_volume-2.0.0.dev2 → mtcli_volume-2.1.0}/mtcli_volume/controllers/volume_controller.py
RENAMED
|
File without changes
|