mtcli-trade 1.6.2__tar.gz → 2.0.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 (42) hide show
  1. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/PKG-INFO +2 -2
  2. mtcli_trade-2.0.0.dev0/mtcli_trade/commands/compra_cli.py +29 -0
  3. mtcli_trade-2.0.0.dev0/mtcli_trade/commands/ordens_cli.py +27 -0
  4. mtcli_trade-2.0.0.dev0/mtcli_trade/commands/posicoes_cli.py +39 -0
  5. mtcli_trade-2.0.0.dev0/mtcli_trade/commands/venda_cli.py +29 -0
  6. mtcli_trade-2.0.0.dev0/mtcli_trade/controllers/compra_controller.py +68 -0
  7. mtcli_trade-1.6.2/mtcli_trade/controllers/orders_controller.py → mtcli_trade-2.0.0.dev0/mtcli_trade/controllers/ordens_controller.py +19 -2
  8. mtcli_trade-2.0.0.dev0/mtcli_trade/controllers/posicoes_controller.py +30 -0
  9. mtcli_trade-2.0.0.dev0/mtcli_trade/controllers/venda_controller.py +68 -0
  10. mtcli_trade-2.0.0.dev0/mtcli_trade/models/compra_model.py +78 -0
  11. mtcli_trade-2.0.0.dev0/mtcli_trade/models/ordem_model.py +103 -0
  12. mtcli_trade-2.0.0.dev0/mtcli_trade/models/ordens_model.py +70 -0
  13. mtcli_trade-2.0.0.dev0/mtcli_trade/models/posicoes_model.py +142 -0
  14. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/mtcli_trade/models/risco_model.py +6 -4
  15. mtcli_trade-2.0.0.dev0/mtcli_trade/models/venda_model.py +78 -0
  16. mtcli_trade-2.0.0.dev0/mtcli_trade/plugin.py +11 -0
  17. mtcli_trade-2.0.0.dev0/mtcli_trade/utils/__init__.py +0 -0
  18. mtcli_trade-2.0.0.dev0/mtcli_trade/utils/mt5_retcode.py +17 -0
  19. mtcli_trade-2.0.0.dev0/mtcli_trade/views/__init__py +0 -0
  20. mtcli_trade-2.0.0.dev0/mtcli_trade/views/ordem_view.py +33 -0
  21. mtcli_trade-2.0.0.dev0/mtcli_trade/views/ordens_view.py +41 -0
  22. mtcli_trade-2.0.0.dev0/mtcli_trade/views/posicoes_view.py +63 -0
  23. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/pyproject.toml +30 -3
  24. mtcli_trade-1.6.2/mtcli_trade/commands/buy.py +0 -69
  25. mtcli_trade-1.6.2/mtcli_trade/commands/cancel.py +0 -58
  26. mtcli_trade-1.6.2/mtcli_trade/commands/orders.py +0 -16
  27. mtcli_trade-1.6.2/mtcli_trade/commands/positions.py +0 -50
  28. mtcli_trade-1.6.2/mtcli_trade/commands/sell.py +0 -72
  29. mtcli_trade-1.6.2/mtcli_trade/commands/zera.py +0 -37
  30. mtcli_trade-1.6.2/mtcli_trade/models/ordem_model.py +0 -69
  31. mtcli_trade-1.6.2/mtcli_trade/models/orders_model.py +0 -29
  32. mtcli_trade-1.6.2/mtcli_trade/models/posicoes_model.py +0 -66
  33. mtcli_trade-1.6.2/mtcli_trade/plugin.py +0 -19
  34. mtcli_trade-1.6.2/mtcli_trade/views/orders_view.py +0 -17
  35. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/LICENSE +0 -0
  36. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/README.md +0 -0
  37. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/mtcli_trade/__init__.py +0 -0
  38. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/mtcli_trade/commands/__init__.py +0 -0
  39. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/mtcli_trade/conf.py +0 -0
  40. {mtcli_trade-1.6.2 → mtcli_trade-2.0.0.dev0}/mtcli_trade/controllers/__init__.py +0 -0
  41. /mtcli_trade-1.6.2/mtcli_trade/models/__init__.py → /mtcli_trade-2.0.0.dev0/mtcli_trade/controllers/ordem_controller.py +0 -0
  42. /mtcli_trade-1.6.2/mtcli_trade/views/__init__py → /mtcli_trade-2.0.0.dev0/mtcli_trade/models/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mtcli-trade
3
- Version: 1.6.2
3
+ Version: 2.0.0.dev0
4
4
  Summary: Plugin mtcli para gerenciamento de órdens
5
5
  License: GPL-3.0
6
6
  Author: Valmir França da Silva
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
- Requires-Dist: mtcli (>=1.18.1)
15
+ Requires-Dist: mtcli (>=3.2.0)
16
16
  Requires-Dist: mtcli-risco (>=2.2.0,<3.0.0)
17
17
  Project-URL: Documentation, https://mtcli-trade.readthedocs.io
18
18
  Project-URL: Homepage, https://github.com/vfranca/mtcli-trade
@@ -0,0 +1,29 @@
1
+ import click
2
+
3
+ from mtcli_trade.conf import LOT, SL, SYMBOL, TP
4
+ from mtcli_trade.controllers.compra_controller import executar_compra
5
+ from mtcli_trade.views.ordem_view import exibir_resultado_ordem
6
+
7
+
8
+ @click.command(
9
+ "compra", help="Envia ordem de compra (market ou limit) com SL e TP opcionais."
10
+ )
11
+ @click.version_option(package_name="mtcli-trade")
12
+ @click.option(
13
+ "--symbol", "-s", default=SYMBOL, help="Símbolo do ativo (default WINV25)."
14
+ )
15
+ @click.option(
16
+ "--lot", type=float, default=LOT, help="Quantidade de contratos (default=1.0)"
17
+ )
18
+ @click.option("--sl", type=float, default=SL, help="Stop loss (em pontos).")
19
+ @click.option("--tp", type=float, default=TP, help="Take profit (em pontos).")
20
+ @click.option("--limit", "-l", is_flag=True, help="Envia ordem pendente (buy limit).")
21
+ @click.option("--preco", "-pr", type=float, default=None, help="Preço da ordem limit.")
22
+ def compra_cmd(symbol, lot, sl, tp, limit, preco):
23
+ """Executa o fluxo completo de compra."""
24
+ resultado = executar_compra(symbol, lot, sl, tp, limit, preco)
25
+ exibir_resultado_ordem(resultado)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ compra_cmd()
@@ -0,0 +1,27 @@
1
+ import click
2
+
3
+ from mtcli_trade.controllers.ordens_controller import (
4
+ cancelar_ordens_pendentes,
5
+ obter_ordens_pendentes,
6
+ )
7
+ from mtcli_trade.views.ordens_view import exibir_cancelar_ordens, exibir_ordens
8
+
9
+
10
+ @click.command("orders", help="Lista ou cancela ordens pendentes no MetaTrader 5.")
11
+ @click.version_option(package_name="mtcli-trade")
12
+ @click.option("--symbol", "-s", default=None, help="Símbolo do ativo para filtrar.")
13
+ @click.option(
14
+ "--cancelar", "-c", is_flag=True, help="Cancela todas as ordens pendentes."
15
+ )
16
+ def ordens_cmd(symbol, cancelar):
17
+ if cancelar:
18
+ resultados = cancelar_ordens_pendentes(symbol)
19
+ exibir_cancelar_ordens(resultados, symbol)
20
+ return
21
+
22
+ ordens = obter_ordens_pendentes(symbol)
23
+ exibir_ordens(ordens)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ ordens_cmd()
@@ -0,0 +1,39 @@
1
+ import click
2
+
3
+ from mtcli_trade.controllers.posicoes_controller import (
4
+ encerrar_posicoes,
5
+ obter_posicoes,
6
+ )
7
+ from mtcli_trade.views.posicoes_view import (
8
+ exibir_posicoes,
9
+ exibir_resultados_encerramento,
10
+ )
11
+
12
+
13
+ @click.command(
14
+ "positions",
15
+ help="Lista ou encerra as posições abertas, mostrando lucro/prejuízo atual e detalhes por ativo.",
16
+ )
17
+ @click.version_option(package_name="mtcli-trade")
18
+ @click.option("--symbol", "-s", default=None, help="Símbolo do ativo (opcional)")
19
+ @click.option(
20
+ "--zerar",
21
+ "-z",
22
+ is_flag=True,
23
+ default=False,
24
+ help="Encerra todas as posições ou de um ativo específico.",
25
+ )
26
+ def posicoes_cmd(symbol, zerar):
27
+ """Lista ou zera todas as posições abertas (ou de um ativo)."""
28
+ posicoes = obter_posicoes(symbol)
29
+
30
+ if zerar:
31
+ resultados = encerrar_posicoes(symbol)
32
+ exibir_resultados_encerramento(resultados)
33
+ return
34
+
35
+ exibir_posicoes(posicoes, symbol)
36
+
37
+
38
+ if __name__ == "__main__":
39
+ posicoes_cmd()
@@ -0,0 +1,29 @@
1
+ import click
2
+
3
+ from mtcli_trade.conf import LOT, SL, SYMBOL, TP
4
+ from mtcli_trade.controllers.venda_controller import executar_venda
5
+ from mtcli_trade.views.ordem_view import exibir_resultado_ordem
6
+
7
+
8
+ @click.command(
9
+ "venda", help="Envia ordem de compra (market ou limit) com SL e TP opcionais."
10
+ )
11
+ @click.version_option(package_name="mtcli-trade")
12
+ @click.option(
13
+ "--symbol", "-s", default=SYMBOL, help="Símbolo do ativo (default WINV25)."
14
+ )
15
+ @click.option(
16
+ "--lot", type=float, default=LOT, help="Quantidade de contratos (default=1.0)"
17
+ )
18
+ @click.option("--sl", type=float, default=SL, help="Stop loss (em pontos).")
19
+ @click.option("--tp", type=float, default=TP, help="Take profit (em pontos).")
20
+ @click.option("--limit", "-l", is_flag=True, help="Envia ordem pendente (buy limit).")
21
+ @click.option("--preco", "-pr", type=float, default=None, help="Preço da ordem limit.")
22
+ def venda_cmd(symbol, lot, sl, tp, limit, preco):
23
+ """Executa o fluxo completo de compra."""
24
+ resultado = executar_venda(symbol, lot, sl, tp, limit, preco)
25
+ exibir_resultado_ordem(resultado)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ venda_cmd()
@@ -0,0 +1,68 @@
1
+ """
2
+ Camada Controller: coordena a execução de ordens de compra.
3
+ """
4
+
5
+ from mtcli.logger import setup_logger
6
+ from mtcli_trade.models.compra_model import (
7
+ enviar_ordem_compra,
8
+ preparar_ordem_compra,
9
+ verificar_risco,
10
+ )
11
+
12
+ log = setup_logger()
13
+
14
+
15
+ def executar_compra(
16
+ symbol: str,
17
+ lot: float,
18
+ sl: float,
19
+ tp: float,
20
+ limit: bool,
21
+ preco: float | None = None,
22
+ ) -> dict:
23
+ """
24
+ Controla o fluxo completo da execução de compra:
25
+ - verifica risco,
26
+ - prepara a ordem,
27
+ - envia ao MetaTrader 5,
28
+ - retorna o resultado estruturado.
29
+ """
30
+
31
+ if verificar_risco():
32
+ log.info("Envio de ordem bloqueado por risco diário.")
33
+ return {"status": "bloqueado", "mensagem": "Limite de prejuízo atingido."}
34
+
35
+ try:
36
+ ordem, is_limit = preparar_ordem_compra(symbol, lot, sl, tp, limit, preco)
37
+ if ordem is None:
38
+ return {
39
+ "status": "falha",
40
+ "mensagem": "Falha ao preparar ordem.",
41
+ "resultado": {
42
+ "tipo": "LIMIT" if limit else "MARKET",
43
+ "preco": preco,
44
+ "retcode": None,
45
+ "mensagem": "Tick inválido ou símbolo não encontrado.",
46
+ },
47
+ }
48
+
49
+ resultado = enviar_ordem_compra(ordem, is_limit)
50
+
51
+ # MetaTrader 5 retorna 10009 = TRADE_RETCODE_DONE (sucesso)
52
+ if resultado.get("retcode") not in (10009,):
53
+ log.warning(f"Ordem recusada pelo MT5: {resultado}")
54
+ return {
55
+ "status": "falha",
56
+ "mensagem": "MetaTrader 5 recusou a ordem.",
57
+ "resultado": resultado,
58
+ }
59
+
60
+ log.info(f"✅ Ordem de compra enviada com sucesso para {symbol}.")
61
+ return {"status": "ok", "resultado": resultado}
62
+
63
+ except ValueError as e:
64
+ log.error(f"Erro de validação: {e}")
65
+ return {"status": "erro", "mensagem": str(e)}
66
+ except Exception as e:
67
+ log.exception(f"Erro inesperado ao executar compra: {e}")
68
+ return {"status": "erro", "mensagem": str(e)}
@@ -1,10 +1,17 @@
1
- from mtcli_trade.models.orders_model import buscar_ordens, formatar_ordem
1
+ from collections.abc import Sequence
2
+
2
3
  from mtcli.logger import setup_logger
4
+ from mtcli_trade.models.ordens_model import (
5
+ buscar_ordens,
6
+ cancelar_ordens,
7
+ formatar_ordem,
8
+ )
3
9
 
4
10
  log = setup_logger()
5
11
 
6
12
 
7
- def obter_ordens_pendentes(symbol=None):
13
+ def obter_ordens_pendentes(symbol: str | None = None) -> Sequence:
14
+ """Retorna lista de ordens pendentes formatadas (pode ser vazia)."""
8
15
  ordens_raw = buscar_ordens(symbol)
9
16
 
10
17
  if not ordens_raw:
@@ -23,3 +30,13 @@ def obter_ordens_pendentes(symbol=None):
23
30
  )
24
31
  ordens.append(dados)
25
32
  return ordens
33
+
34
+
35
+ def cancelar_ordens_pendentes(symbol: str | None = None):
36
+ """Cancela ordens pendentes via model e retorna resultados."""
37
+ try:
38
+ resultados = cancelar_ordens(symbol)
39
+ except Exception as e:
40
+ log.error(f"Erro ao cancelar ordens: {e}")
41
+ raise
42
+ return resultados
@@ -0,0 +1,30 @@
1
+ from collections.abc import Sequence
2
+
3
+ from mtcli.logger import setup_logger
4
+ from mtcli_trade.models.posicoes_model import buscar_posicoes, encerra_posicoes
5
+
6
+ log = setup_logger()
7
+
8
+
9
+ def obter_posicoes(symbol: str | None = None) -> Sequence:
10
+ """Retorna uma sequência (possivelmente vazia) de posições abertas para o symbol ou todas."""
11
+ try:
12
+ posicoes = buscar_posicoes(symbol)
13
+ except Exception as e:
14
+ log.error(f"Erro ao buscar posições: {e}")
15
+ raise
16
+
17
+ # normalize: sempre devolve lista/sequence (não None)
18
+ if not posicoes:
19
+ return []
20
+ return posicoes
21
+
22
+
23
+ def encerrar_posicoes(symbol: str | None = None):
24
+ """Encerrar posições (retorna a lista de resultados produzida pelo model)."""
25
+ try:
26
+ resultados = encerra_posicoes(symbol)
27
+ except Exception as e:
28
+ log.error(f"Erro ao encerrar posições: {e}")
29
+ raise
30
+ return resultados
@@ -0,0 +1,68 @@
1
+ """
2
+ Camada Controller: coordena a execução de ordens de venda.
3
+ """
4
+
5
+ from mtcli.logger import setup_logger
6
+ from mtcli_trade.models.venda_model import (
7
+ enviar_ordem_venda,
8
+ preparar_ordem_venda,
9
+ verificar_risco,
10
+ )
11
+
12
+ log = setup_logger()
13
+
14
+
15
+ def executar_venda(
16
+ symbol: str,
17
+ lot: float,
18
+ sl: float,
19
+ tp: float,
20
+ limit: bool,
21
+ preco: float | None = None,
22
+ ) -> dict:
23
+ """
24
+ Controla o fluxo completo da execução de venda:
25
+ - verifica risco,
26
+ - prepara a ordem,
27
+ - envia ao MetaTrader 5,
28
+ - retorna o resultado estruturado.
29
+ """
30
+
31
+ if verificar_risco():
32
+ log.info("Envio de ordem bloqueado por risco diário.")
33
+ return {"status": "bloqueado", "mensagem": "Limite de prejuízo atingido."}
34
+
35
+ try:
36
+ ordem, is_limit = preparar_ordem_venda(symbol, lot, sl, tp, limit, preco)
37
+ if ordem is None:
38
+ return {
39
+ "status": "falha",
40
+ "mensagem": "Falha ao preparar ordem.",
41
+ "resultado": {
42
+ "tipo": "LIMIT" if limit else "MARKET",
43
+ "preco": preco,
44
+ "retcode": None,
45
+ "mensagem": "Tick inválido ou símbolo não encontrado.",
46
+ },
47
+ }
48
+
49
+ resultado = enviar_ordem_venda(ordem, is_limit)
50
+
51
+ # MetaTrader 5 retorna 10009 = TRADE_RETCODE_DONE (sucesso)
52
+ if resultado.get("retcode") not in (10009,):
53
+ log.warning(f"Ordem recusada pelo MT5: {resultado}")
54
+ return {
55
+ "status": "falha",
56
+ "mensagem": "MetaTrader 5 recusou a ordem.",
57
+ "resultado": resultado,
58
+ }
59
+
60
+ log.info(f"✅ Ordem de compra enviada com sucesso para {symbol}.")
61
+ return {"status": "ok", "resultado": resultado}
62
+
63
+ except ValueError as e:
64
+ log.error(f"Erro de validação: {e}")
65
+ return {"status": "erro", "mensagem": str(e)}
66
+ except Exception as e:
67
+ log.exception(f"Erro inesperado ao executar venda: {e}")
68
+ return {"status": "erro", "mensagem": str(e)}
@@ -0,0 +1,78 @@
1
+ """
2
+ Camada Model responsável pela lógica de ordens de compra no MetaTrader 5.
3
+ """
4
+
5
+ import MetaTrader5 as mt5
6
+
7
+ from mtcli.logger import setup_logger
8
+ from mtcli_trade.conf import LOSS_LIMIT, STATUS_FILE
9
+ from mtcli_trade.models.ordem_model import criar_ordem, enviar_ordem, inicializar
10
+ from mtcli_trade.models.risco_model import controlar_risco
11
+
12
+ log = setup_logger()
13
+
14
+
15
+ def verificar_risco() -> bool:
16
+ """Retorna True se o controle de risco bloquear a operação."""
17
+ try:
18
+ return controlar_risco(STATUS_FILE, LOSS_LIMIT)
19
+ except Exception as e:
20
+ log.exception(f"Erro ao verificar risco: {e}")
21
+ return True
22
+
23
+
24
+ def preparar_ordem_compra(
25
+ symbol: str,
26
+ lot: float,
27
+ sl: float,
28
+ tp: float,
29
+ limit: bool,
30
+ preco: float | None = None,
31
+ ):
32
+ """
33
+ Prepara uma ordem de compra (market ou limit) para envio.
34
+
35
+ Retorna:
36
+ tuple: (ordem, limit) se sucesso, ou (None, None) se falha.
37
+ """
38
+ tick = inicializar(symbol)
39
+ if not tick:
40
+ log.error(f"Falha ao inicializar tick para símbolo {symbol}")
41
+ return None, None
42
+
43
+ if limit:
44
+ if preco is None:
45
+ raise ValueError("Preço obrigatório para ordens pendentes (--limit).")
46
+ price = preco
47
+ order_type = mt5.ORDER_TYPE_BUY_LIMIT
48
+ else:
49
+ price = tick.ask
50
+ order_type = mt5.ORDER_TYPE_BUY
51
+
52
+ ordem = criar_ordem(symbol, lot, sl, tp, price, order_type, limit)
53
+ log.debug(f"Ordem preparada: {ordem}")
54
+ return ordem, limit
55
+
56
+
57
+ def enviar_ordem_compra(ordem: dict, limit: bool):
58
+ """
59
+ Envia uma ordem de compra válida para o MetaTrader 5.
60
+
61
+ Args:
62
+ ordem (dict): Estrutura de ordem gerada pelo Model.
63
+ limit (bool): Define se é ordem limitada (True) ou a mercado (False).
64
+
65
+ Returns:
66
+ dict: Resultado retornado pela função `enviar_ordem()`.
67
+ """
68
+ if not ordem:
69
+ log.error("Ordem inválida, cancelando envio.")
70
+ return {"retcode": None, "comment": "Ordem inválida."}
71
+
72
+ try:
73
+ resultado = enviar_ordem(ordem, limit)
74
+ log.debug(f"Resultado do envio da ordem: {resultado}")
75
+ return resultado
76
+ except Exception as e:
77
+ log.exception(f"Erro ao enviar ordem: {e}")
78
+ return {"retcode": None, "comment": str(e)}
@@ -0,0 +1,103 @@
1
+ """
2
+ Modelo de execução de ordens no MetaTrader 5 (camada Model do MVC).
3
+ Responsável por criar, enviar e inicializar ordens de compra/venda.
4
+ """
5
+
6
+ import MetaTrader5 as mt5
7
+
8
+ from mtcli.logger import setup_logger
9
+ from mtcli.mt5_context import mt5_conexao
10
+
11
+ log = setup_logger()
12
+
13
+
14
+ def inicializar(symbol: str):
15
+ """
16
+ Inicializa o símbolo no MetaTrader 5 e retorna seu tick atual.
17
+ Retorna None se houver falha na seleção ou na obtenção do tick.
18
+ """
19
+ with mt5_conexao():
20
+ if not mt5.symbol_select(symbol, True):
21
+ log.error(f"Erro ao selecionar símbolo {symbol}")
22
+ return None
23
+
24
+ tick = mt5.symbol_info_tick(symbol)
25
+ if not tick:
26
+ log.error(f"Erro ao obter cotação de {symbol}")
27
+ return None
28
+
29
+ log.debug(f"Símbolo {symbol} inicializado com sucesso.")
30
+ return tick
31
+
32
+
33
+ def criar_ordem(
34
+ symbol: str,
35
+ lot: float,
36
+ sl: float,
37
+ tp: float,
38
+ price: float,
39
+ order_type: int,
40
+ limit: bool,
41
+ ) -> dict:
42
+ """
43
+ Cria e retorna um dicionário com os parâmetros de uma ordem MT5.
44
+ Suporta ordens a mercado e ordens pendentes (limit).
45
+ """
46
+ try:
47
+ info = mt5.symbol_info(symbol)
48
+ point = getattr(info, "point", 0.01)
49
+ except Exception:
50
+ point = 0.01
51
+ log.warning(
52
+ f"Símbolo {symbol} sem informações completas; usando point padrão 0.01."
53
+ )
54
+
55
+ sl_price = (
56
+ price - sl * point
57
+ if order_type in (mt5.ORDER_TYPE_BUY, mt5.ORDER_TYPE_BUY_LIMIT)
58
+ else price + sl * point
59
+ )
60
+ tp_price = (
61
+ price + tp * point
62
+ if order_type in (mt5.ORDER_TYPE_BUY, mt5.ORDER_TYPE_BUY_LIMIT)
63
+ else price - tp * point
64
+ )
65
+
66
+ ordem = {
67
+ "action": mt5.TRADE_ACTION_PENDING if limit else mt5.TRADE_ACTION_DEAL,
68
+ "symbol": symbol,
69
+ "volume": lot,
70
+ "type": order_type,
71
+ "price": price,
72
+ "sl": sl_price,
73
+ "tp": tp_price,
74
+ "deviation": 10,
75
+ "magic": 1000,
76
+ "comment": "Ordem mtcli",
77
+ "type_time": mt5.ORDER_TIME_DAY,
78
+ "type_filling": mt5.ORDER_FILLING_IOC,
79
+ }
80
+
81
+ log.debug(f"Ordem criada: {ordem}")
82
+ return ordem
83
+
84
+
85
+ def enviar_ordem(ordem, limit: bool):
86
+ """Envia a ordem e retorna resultado formatado, mesmo em falha."""
87
+ resultado = mt5.order_send(ordem)
88
+
89
+ if resultado is None:
90
+ return {
91
+ "retcode": None,
92
+ "comment": "Erro: ordem não enviada. Possível mercado fechado.",
93
+ }
94
+
95
+ return {
96
+ "retcode": resultado.retcode,
97
+ "comment": getattr(resultado, "comment", ""),
98
+ "order": getattr(resultado, "order", None),
99
+ "symbol": ordem["symbol"],
100
+ "type": "LIMIT" if limit else "MARKET",
101
+ "volume": ordem["volume"],
102
+ "price": ordem["price"],
103
+ }
@@ -0,0 +1,70 @@
1
+ from typing import Any
2
+
3
+ import MetaTrader5 as mt5
4
+
5
+ from mtcli.logger import setup_logger
6
+ from mtcli.mt5_context import mt5_conexao
7
+ from mtcli_trade.conf import DIGITOS
8
+
9
+ log = setup_logger()
10
+
11
+
12
+ def buscar_ordens(symbol: str | None = None):
13
+ """Recupera ordens pendentes do MetaTrader 5."""
14
+ with mt5_conexao():
15
+ return mt5.orders_get(symbol=symbol) if symbol else mt5.orders_get()
16
+
17
+
18
+ def formatar_ordem(ordem):
19
+ tipo = (
20
+ "COMPRA"
21
+ if ordem.type == mt5.ORDER_TYPE_BUY_LIMIT
22
+ else "VENDA"
23
+ if ordem.type == mt5.ORDER_TYPE_SELL_LIMIT
24
+ else str(ordem.type)
25
+ )
26
+ return {
27
+ "tipo": tipo,
28
+ "symbol": ordem.symbol,
29
+ "volume": ordem.volume_current,
30
+ "preco": round(ordem.price_open, DIGITOS),
31
+ "ticket": ordem.ticket,
32
+ }
33
+
34
+
35
+ def cancelar_ordens(symbol: str | None = None) -> list[Any]:
36
+ """Cancela todas as ordens (ou as de um símbolo específico)."""
37
+ resultados = []
38
+ with mt5_conexao():
39
+ ordens = mt5.orders_get(symbol=symbol) if symbol else mt5.orders_get()
40
+
41
+ if not ordens:
42
+ log.info(
43
+ f"Nenhuma ordem pendente para {symbol}"
44
+ if symbol
45
+ else "Nenhuma ordem pendente encontrada."
46
+ )
47
+ return resultados
48
+
49
+ for ordem in ordens:
50
+ req = {
51
+ "action": mt5.TRADE_ACTION_REMOVE,
52
+ "order": ordem.ticket,
53
+ "symbol": ordem.symbol,
54
+ "magic": 1000,
55
+ "comment": "Cancelamento via CLI",
56
+ }
57
+
58
+ resultado = mt5.order_send(req)
59
+ if (
60
+ resultado
61
+ and getattr(resultado, "retcode", None) == mt5.TRADE_RETCODE_DONE
62
+ ):
63
+ log.info(f"Ordem cancelada: ticket {ordem.ticket} ({ordem.symbol})")
64
+ else:
65
+ log.error(
66
+ f"Falha ao cancelar {ordem.ticket} código {getattr(resultado, 'retcode', 'N/A')}"
67
+ )
68
+ resultados.append(resultado)
69
+
70
+ return resultados