mtcli 3.8.0.dev12__py3-none-any.whl → 3.8.0.dev13__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.
mtcli/cli.py CHANGED
@@ -1,119 +1,119 @@
1
- """
2
- CLI principal do mtcli.
3
-
4
- Responsável por:
5
-
6
- - inicializar o ambiente do CLI
7
- - carregar plugins
8
- - iniciar captura automática de ticks (opcional)
9
-
10
- A captura automática é ativada se a variável de ambiente
11
- MTCLI_SYMBOL estiver definida.
12
-
13
- Exemplo:
14
-
15
- MTCLI_SYMBOL=WIN$N mt bars
16
-
17
- Nesse caso o mtcli inicia um TickEngine em background
18
- para manter o histórico de ticks atualizado.
19
- """
20
-
21
- import os
22
- import click
23
-
24
- from mtcli.plugin_loader import load_plugins
25
- from mtcli.logger import setup_logger
26
- from mtcli.services.tick_service import ensure_tick_engine
27
-
28
- from .commands.bars import bars
29
- from .commands.doctor import doctor
30
- from .commands.ticks import ticks
31
- from .commands.backfill import backfill
32
-
33
-
34
- logger = setup_logger(__name__)
35
-
36
- _tick_engine = None
37
-
38
-
39
- def start_tick_capture():
40
- """
41
- Inicia captura contínua de ticks em background.
42
-
43
- Se a variável de ambiente MTCLI_SYMBOL estiver definida,
44
- o mtcli iniciará automaticamente um TickEngine para
45
- coletar ticks continuamente.
46
-
47
- Isso permite manter um histórico próprio de ticks
48
- independente do histórico do broker.
49
- """
50
-
51
- global _tick_engine
52
-
53
- if _tick_engine:
54
- return
55
-
56
- symbol = os.getenv("MTCLI_SYMBOL")
57
-
58
- if not symbol:
59
- logger.info(
60
- "Captura automática de ticks desativada (MTCLI_SYMBOL não definido)."
61
- )
62
- return
63
-
64
- logger.info("Iniciando captura contínua de ticks para %s", symbol)
65
-
66
- try:
67
-
68
- # CORREÇÃO PRINCIPAL
69
- _tick_engine = ensure_tick_engine([symbol])
70
-
71
- logger.info("Captura de ticks iniciada em background.")
72
-
73
- except Exception:
74
- logger.exception("Falha ao iniciar captura de ticks")
75
-
76
-
77
- @click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
78
- @click.version_option(package_name="mtcli")
79
- @click.pass_context
80
- def mt(ctx):
81
- """
82
- CLI principal do mtcli.
83
- """
84
-
85
- start_tick_capture()
86
-
87
- if ctx.invoked_subcommand is None:
88
- click.echo(ctx.get_help())
89
-
90
-
91
- mt.add_command(doctor, name="doctor")
92
- mt.add_command(bars, name="bars")
93
- mt.add_command(doctor, name="dr")
94
- mt.add_command(ticks)
95
- mt.add_command(backfill, name="fill")
96
-
97
- loaded_plugins = load_plugins(mt)
98
-
99
- logger.info("Plugins carregados: %s", loaded_plugins)
100
-
101
-
102
- @mt.command(name="plugins")
103
- def list_plugins():
104
- """
105
- Lista os plugins carregados no mtcli.
106
- """
107
-
108
- if not loaded_plugins:
109
- click.echo("Nenhum plugin carregado.")
110
- return
111
-
112
- click.echo("Plugins carregados:\n")
113
-
114
- for name in loaded_plugins:
115
- click.echo(f" {name}")
116
-
117
-
118
- if __name__ == "__main__":
119
- mt()
1
+ """
2
+ CLI principal do mtcli.
3
+
4
+ Responsável por:
5
+
6
+ - inicializar o ambiente do CLI
7
+ - carregar plugins
8
+ - iniciar captura automática de ticks (opcional)
9
+
10
+ A captura automática é ativada se a variável de ambiente
11
+ MTCLI_SYMBOL estiver definida.
12
+
13
+ Exemplo:
14
+
15
+ MTCLI_SYMBOL=WIN$N mt bars
16
+
17
+ Nesse caso o mtcli inicia um TickEngine em background
18
+ para manter o histórico de ticks atualizado.
19
+ """
20
+
21
+ import os
22
+ import click
23
+
24
+ from mtcli.plugin_loader import load_plugins
25
+ from mtcli.logger import setup_logger
26
+ from mtcli.services.tick_service import ensure_tick_engine
27
+
28
+ from .commands.bars import bars
29
+ from .commands.doctor import doctor
30
+ from .commands.ticks import ticks
31
+ from .commands.backfill import backfill
32
+
33
+
34
+ logger = setup_logger(__name__)
35
+
36
+ _tick_engine = None
37
+
38
+
39
+ def start_tick_capture():
40
+ """
41
+ Inicia captura contínua de ticks em background.
42
+
43
+ Se a variável de ambiente MTCLI_SYMBOL estiver definida,
44
+ o mtcli iniciará automaticamente um TickEngine para
45
+ coletar ticks continuamente.
46
+
47
+ Isso permite manter um histórico próprio de ticks
48
+ independente do histórico do broker.
49
+ """
50
+
51
+ global _tick_engine
52
+
53
+ if _tick_engine:
54
+ return
55
+
56
+ symbol = os.getenv("MTCLI_SYMBOL")
57
+
58
+ if not symbol:
59
+ logger.info(
60
+ "Captura automática de ticks desativada (MTCLI_SYMBOL não definido)."
61
+ )
62
+ return
63
+
64
+ logger.info("Iniciando captura contínua de ticks para %s", symbol)
65
+
66
+ try:
67
+
68
+ # CORREÇÃO PRINCIPAL
69
+ _tick_engine = ensure_tick_engine([symbol])
70
+
71
+ logger.info("Captura de ticks iniciada em background.")
72
+
73
+ except Exception:
74
+ logger.exception("Falha ao iniciar captura de ticks")
75
+
76
+
77
+ @click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
78
+ @click.version_option(package_name="mtcli")
79
+ @click.pass_context
80
+ def mt(ctx):
81
+ """
82
+ CLI principal do mtcli.
83
+ """
84
+
85
+ start_tick_capture()
86
+
87
+ if ctx.invoked_subcommand is None:
88
+ click.echo(ctx.get_help())
89
+
90
+
91
+ mt.add_command(doctor, name="doctor")
92
+ mt.add_command(bars, name="bars")
93
+ mt.add_command(doctor, name="dr")
94
+ mt.add_command(ticks)
95
+ mt.add_command(backfill, name="fill")
96
+
97
+ loaded_plugins = load_plugins(mt)
98
+
99
+ logger.info("Plugins carregados: %s", loaded_plugins)
100
+
101
+
102
+ @mt.command(name="plugins")
103
+ def list_plugins():
104
+ """
105
+ Lista os plugins carregados no mtcli.
106
+ """
107
+
108
+ if not loaded_plugins:
109
+ click.echo("Nenhum plugin carregado.")
110
+ return
111
+
112
+ click.echo("Plugins carregados:\n")
113
+
114
+ for name in loaded_plugins:
115
+ click.echo(f" {name}")
116
+
117
+
118
+ if __name__ == "__main__":
119
+ mt()
@@ -1,68 +1,68 @@
1
- """
2
- Comando CLI: backfill
3
-
4
- Responsável por carregar histórico de ticks do MetaTrader5
5
- para o banco SQLite do mtcli utilizando o BackfillEngine.
6
-
7
- Fluxo:
8
-
9
- BackfillEngine
10
-
11
- TickRepository
12
-
13
- SQLite
14
-
15
- Opcionalmente os ticks também podem ser publicados no TickBus
16
- para que plugins consumam o fluxo histórico.
17
- """
18
-
19
- import click
20
-
21
- from mtcli.marketdata.backfill_engine import BackfillEngine
22
- from mtcli.marketdata.tick_bus import TickBus
23
- from mtcli.marketdata.tick_repository import TickRepository
24
-
25
-
26
- @click.command()
27
- @click.argument("symbol")
28
- @click.option(
29
- "--days",
30
- default=5,
31
- show_default=True,
32
- help="Número de dias de histórico a carregar caso não exista histórico local.",
33
- )
34
- def backfill(symbol: str, days: int):
35
- """
36
- Executa backfill histórico de ticks.
37
-
38
- Este comando baixa ticks históricos diretamente do MetaTrader5
39
- e os grava no banco local SQLite do mtcli.
40
-
41
- O processo é incremental:
42
-
43
- - se já existirem ticks no banco, o backfill continua do último tick
44
- - caso contrário, carrega o número de dias definido em --days
45
-
46
- Examples
47
- --------
48
-
49
- Carregar 5 dias:
50
-
51
- mt fill WINJ26
52
-
53
- Carregar 30 dias:
54
-
55
- mt fill WINJ26 --days 30
56
- """
57
-
58
- # Event bus (permite que plugins consumam os ticks históricos)
59
- bus = TickBus()
60
-
61
- # Repositório de persistência
62
- repo = TickRepository()
63
-
64
- # Engine de backfill
65
- engine = BackfillEngine(symbol, bus, repo)
66
-
67
- # Executa o carregamento histórico
68
- engine.run(days)
1
+ """
2
+ Comando CLI: backfill
3
+
4
+ Responsável por carregar histórico de ticks do MetaTrader5
5
+ para o banco SQLite do mtcli utilizando o BackfillEngine.
6
+
7
+ Fluxo:
8
+
9
+ BackfillEngine
10
+
11
+ TickRepository
12
+
13
+ SQLite
14
+
15
+ Opcionalmente os ticks também podem ser publicados no TickBus
16
+ para que plugins consumam o fluxo histórico.
17
+ """
18
+
19
+ import click
20
+
21
+ from mtcli.marketdata.backfill_engine import BackfillEngine
22
+ from mtcli.marketdata.tick_bus import TickBus
23
+ from mtcli.marketdata.tick_repository import TickRepository
24
+
25
+
26
+ @click.command()
27
+ @click.argument("symbol")
28
+ @click.option(
29
+ "--days",
30
+ default=5,
31
+ show_default=True,
32
+ help="Número de dias de histórico a carregar caso não exista histórico local.",
33
+ )
34
+ def backfill(symbol: str, days: int):
35
+ """
36
+ Executa backfill histórico de ticks.
37
+
38
+ Este comando baixa ticks históricos diretamente do MetaTrader5
39
+ e os grava no banco local SQLite do mtcli.
40
+
41
+ O processo é incremental:
42
+
43
+ - se já existirem ticks no banco, o backfill continua do último tick
44
+ - caso contrário, carrega o número de dias definido em --days
45
+
46
+ Examples
47
+ --------
48
+
49
+ Carregar 5 dias:
50
+
51
+ mt fill WINJ26
52
+
53
+ Carregar 30 dias:
54
+
55
+ mt fill WINJ26 --days 30
56
+ """
57
+
58
+ # Event bus (permite que plugins consumam os ticks históricos)
59
+ bus = TickBus()
60
+
61
+ # Repositório de persistência
62
+ repo = TickRepository()
63
+
64
+ # Engine de backfill
65
+ engine = BackfillEngine(symbol, bus, repo)
66
+
67
+ # Executa o carregamento histórico
68
+ engine.run(days)
mtcli/commands/ticks.py CHANGED
@@ -1,44 +1,44 @@
1
- """
2
- Comando CLI para captura contínua de ticks.
3
-
4
- Permite iniciar o serviço de captura de ticks
5
- para um ou mais símbolos.
6
-
7
- Exemplo:
8
-
9
- mt ticks WIN$N
10
- mt ticks WIN$N WDO$N PETR4
11
- """
12
-
13
- import time
14
- import click
15
-
16
- from mtcli.services.tick_service import ensure_tick_engine
17
-
18
-
19
- @click.command()
20
- @click.argument("symbols", nargs=-1)
21
- def ticks(symbols):
22
- """
23
- Inicia captura contínua de ticks.
24
- """
25
-
26
- if not symbols:
27
- click.echo("Informe ao menos um símbolo.")
28
- return
29
-
30
- # atualmente o engine suporta apenas 1 símbolo
31
- symbol = symbols[0]
32
-
33
- engine = ensure_tick_engine(symbol)
34
-
35
- click.echo(f"Captura de ticks iniciada para {symbol}")
36
-
37
- try:
38
- # inicia engine
39
- engine.start()
40
-
41
- except KeyboardInterrupt:
42
-
43
- click.echo("\nEncerrando captura de ticks...")
44
- engine.stop()
1
+ """
2
+ Comando CLI para captura contínua de ticks.
3
+
4
+ Permite iniciar o serviço de captura de ticks
5
+ para um ou mais símbolos.
6
+
7
+ Exemplo:
8
+
9
+ mt ticks WIN$N
10
+ mt ticks WIN$N WDO$N PETR4
11
+ """
12
+
13
+ import time
14
+ import click
15
+
16
+ from mtcli.services.tick_service import ensure_tick_engine
17
+
18
+
19
+ @click.command()
20
+ @click.argument("symbols", nargs=-1)
21
+ def ticks(symbols):
22
+ """
23
+ Inicia captura contínua de ticks.
24
+ """
25
+
26
+ if not symbols:
27
+ click.echo("Informe ao menos um símbolo.")
28
+ return
29
+
30
+ # atualmente o engine suporta apenas 1 símbolo
31
+ symbol = symbols[0]
32
+
33
+ engine = ensure_tick_engine(symbol)
34
+
35
+ click.echo(f"Captura de ticks iniciada para {symbol}")
36
+
37
+ try:
38
+ # inicia engine
39
+ engine.start()
40
+
41
+ except KeyboardInterrupt:
42
+
43
+ click.echo("\nEncerrando captura de ticks...")
44
+ engine.stop()
mtcli/database.py CHANGED
@@ -15,14 +15,30 @@ from datetime import datetime
15
15
 
16
16
  from .conf import DB_NAME
17
17
  from .migrations.runner import run_migrations
18
+ from .logger import setup_logger
18
19
 
19
20
 
21
+ # ==========================================================
22
+ # LOGGER
23
+ # ==========================================================
24
+
25
+ log = setup_logger(__name__)
26
+
27
+
28
+ # ==========================================================
29
+ # PATHS
30
+ # ==========================================================
31
+
20
32
  DB_PATH = Path.home() / ".mtcli" / DB_NAME
21
33
  BACKUP_DIR = Path.home() / ".mtcli" / "backups"
22
34
 
23
35
  _connection = None
24
36
 
25
37
 
38
+ # ==========================================================
39
+ # CONNECTION
40
+ # ==========================================================
41
+
26
42
  def get_connection():
27
43
  """
28
44
  Retorna conexão singleton SQLite.
@@ -31,14 +47,26 @@ def get_connection():
31
47
  global _connection
32
48
 
33
49
  if _connection:
50
+ log.debug("Reutilizando conexão SQLite existente.")
34
51
  return _connection
35
52
 
53
+ log.debug("Inicializando conexão SQLite.")
54
+
36
55
  DB_PATH.parent.mkdir(parents=True, exist_ok=True)
37
56
  BACKUP_DIR.mkdir(parents=True, exist_ok=True)
38
57
 
58
+ log.debug("Diretórios garantidos: db=%s backups=%s", DB_PATH.parent, BACKUP_DIR)
59
+
39
60
  conn = sqlite3.connect(DB_PATH, check_same_thread=False)
40
61
 
41
- # otimizações
62
+ log.debug("Conexão SQLite criada em %s", DB_PATH)
63
+
64
+ # ======================================================
65
+ # PRAGMAS DE OTIMIZAÇÃO
66
+ # ======================================================
67
+
68
+ log.debug("Aplicando otimizações PRAGMA.")
69
+
42
70
  conn.execute("PRAGMA journal_mode=WAL")
43
71
  conn.execute("PRAGMA synchronous=NORMAL")
44
72
  conn.execute("PRAGMA temp_store=MEMORY")
@@ -48,11 +76,25 @@ def get_connection():
48
76
  conn.execute("PRAGMA wal_autocheckpoint=5000")
49
77
  conn.execute("PRAGMA foreign_keys=ON")
50
78
 
51
- # executa migrations automaticamente
52
- run_migrations(conn)
79
+ log.debug("PRAGMAs aplicadas com sucesso.")
80
+
81
+ # ======================================================
82
+ # MIGRATIONS
83
+ # ======================================================
84
+
85
+ log.debug("Executando migrations do banco de dados.")
86
+
87
+ try:
88
+ run_migrations(conn)
89
+ log.info("Migrations executadas/verificadas com sucesso.")
90
+ except Exception:
91
+ log.exception("Erro ao executar migrations.")
92
+ raise
53
93
 
54
94
  _connection = conn
55
95
 
96
+ log.debug("Conexão SQLite inicializada e armazenada como singleton.")
97
+
56
98
  return conn
57
99
 
58
100
 
@@ -61,17 +103,35 @@ def get_connection():
61
103
  # ==========================================================
62
104
 
63
105
  def backup_database(conn):
106
+ """
107
+ Cria backup diário do banco de dados SQLite.
108
+
109
+ O backup é realizado apenas uma vez por dia.
110
+ """
64
111
 
65
112
  now = datetime.now().strftime("%Y%m%d")
66
113
 
67
114
  backup_path = BACKUP_DIR / f"marketdata_{now}.db"
68
115
 
116
+ log.debug("Verificando necessidade de backup diário: %s", backup_path)
117
+
69
118
  if backup_path.exists():
119
+ log.debug("Backup diário já existe. Ignorando backup.")
70
120
  return
71
121
 
72
- backup_conn = sqlite3.connect(backup_path)
122
+ log.info("Iniciando backup do banco para %s", backup_path)
123
+
124
+ try:
125
+
126
+ backup_conn = sqlite3.connect(backup_path)
127
+
128
+ with backup_conn:
129
+ conn.backup(backup_conn)
130
+
131
+ backup_conn.close()
73
132
 
74
- with backup_conn:
75
- conn.backup(backup_conn)
133
+ log.info("Backup concluído com sucesso.")
76
134
 
77
- backup_conn.close()
135
+ except Exception:
136
+ log.exception("Falha durante backup do banco.")
137
+ raise