mtcli 3.8.0.dev0__tar.gz → 3.8.0.dev2__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 (82) hide show
  1. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/PKG-INFO +1 -1
  2. mtcli-3.8.0.dev2/mtcli/cli.py +105 -0
  3. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/database.py +40 -3
  4. mtcli-3.8.0.dev2/mtcli/marketdata/tick_cache.py +61 -0
  5. mtcli-3.8.0.dev2/mtcli/marketdata/tick_engine.py +93 -0
  6. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/marketdata/tick_repository.py +35 -9
  7. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/marketdata/tick_streamer.py +4 -4
  8. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/pyproject.toml +1 -1
  9. mtcli-3.8.0.dev0/mtcli/cli.py +0 -135
  10. mtcli-3.8.0.dev0/mtcli/marketdata/tick_cache.py +0 -38
  11. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/LICENSE +0 -0
  12. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/README.md +0 -0
  13. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/__init__.py +0 -0
  14. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/commands/__init__.py +0 -0
  15. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/commands/bars.py +0 -0
  16. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/commands/conf.py +0 -0
  17. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/commands/doctor.py +0 -0
  18. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/commands/migrate.py +0 -0
  19. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/conecta.py +0 -0
  20. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/conf.py +0 -0
  21. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/config_registre.py +0 -0
  22. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/data/__init__.py +0 -0
  23. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/data/base.py +0 -0
  24. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/data/csv.py +0 -0
  25. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/data/mt5.py +0 -0
  26. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/domain/__init__.py +0 -0
  27. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/domain/timeframe.py +0 -0
  28. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/logger.py +0 -0
  29. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/marketdata/__init__.py +0 -0
  30. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/migrations/001_initial_schema.py +0 -0
  31. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/migrations/002_ticks_time_msc.py +0 -0
  32. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
  33. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/migrations/__init__.py +0 -0
  34. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/migrations/runner.py +0 -0
  35. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/__init__.py +0 -0
  36. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/bar_model.py +0 -0
  37. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/bars_model.py +0 -0
  38. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/chart_model.py +0 -0
  39. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/conf_model.py +0 -0
  40. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/consecutive_bars_model.py +0 -0
  41. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/rates_model.py +0 -0
  42. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/signals_model.py +0 -0
  43. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/models/unconsecutive_bar_model.py +0 -0
  44. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/mt5_context.py +0 -0
  45. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugin.py +0 -0
  46. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugin_loader.py +0 -0
  47. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugin_manager.py +0 -0
  48. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/__init__.py +0 -0
  49. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/exemplo.py-dist +0 -0
  50. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/__init__.py +0 -0
  51. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/cli.py +0 -0
  52. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/conf.py +0 -0
  53. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  54. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  55. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  56. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  57. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  58. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/__init__.py +0 -0
  59. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/cli.py +0 -0
  60. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/conf.py +0 -0
  61. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  62. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  63. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  64. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  65. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/__init__.py +0 -0
  66. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/cli.py +0 -0
  67. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/conf.py +0 -0
  68. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  69. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  70. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  71. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  72. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/__init__.py +0 -0
  73. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/close_view.py +0 -0
  74. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/full_view.py +0 -0
  75. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/high_view.py +0 -0
  76. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/low_view.py +0 -0
  77. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/min_view.py +0 -0
  78. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/open_view.py +0 -0
  79. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/ranges_view.py +0 -0
  80. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/rates_view.py +0 -0
  81. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/vars_view.py +0 -0
  82. {mtcli-3.8.0.dev0 → mtcli-3.8.0.dev2}/mtcli/views/volumes_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtcli
3
- Version: 3.8.0.dev0
3
+ Version: 3.8.0.dev2
4
4
  Summary: Aplicativo CLI para exibir gráficos do MetaTrader 5 screen reader friendly
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -0,0 +1,105 @@
1
+ """
2
+ CLI principal do mtcli.
3
+ """
4
+
5
+ import os
6
+ import threading
7
+ import click
8
+
9
+ from mtcli.plugin_loader import load_plugins
10
+ from mtcli.logger import setup_logger
11
+ from mtcli.marketdata.tick_streamer import TickStreamer
12
+
13
+ from .commands.bars import bars
14
+ from .commands.doctor import doctor
15
+ from .commands.migrate import migrate
16
+
17
+
18
+ logger = setup_logger(__name__)
19
+
20
+ _tick_streamer = None
21
+
22
+
23
+ def start_tick_capture():
24
+ """
25
+ Inicia captura contínua de ticks em background.
26
+
27
+ O símbolo deve ser definido via variável:
28
+
29
+ MTCLI_SYMBOL=WINJ26
30
+ """
31
+
32
+ global _tick_streamer
33
+
34
+ if _tick_streamer:
35
+ return
36
+
37
+ symbol = os.getenv("MTCLI_SYMBOL")
38
+
39
+ if not symbol:
40
+ logger.info("Captura de ticks desativada (MTCLI_SYMBOL não definido).")
41
+ return
42
+
43
+ logger.info("Iniciando captura contínua de ticks para %s", symbol)
44
+
45
+ try:
46
+
47
+ _tick_streamer = TickStreamer(symbol)
48
+
49
+ thread = threading.Thread(
50
+ target=_tick_streamer.start,
51
+ daemon=True,
52
+ name="mtcli-tick-streamer",
53
+ )
54
+
55
+ thread.start()
56
+
57
+ logger.info("Captura de ticks iniciada em background.")
58
+
59
+ except Exception:
60
+
61
+ logger.exception("Falha ao iniciar captura de ticks")
62
+
63
+
64
+ @click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
65
+ @click.version_option(package_name="mtcli")
66
+ @click.pass_context
67
+ def mt(ctx):
68
+ """
69
+ CLI principal do mtcli.
70
+ """
71
+
72
+ start_tick_capture()
73
+
74
+ if ctx.invoked_subcommand is None:
75
+ click.echo(ctx.get_help())
76
+
77
+
78
+ mt.add_command(doctor, name="doctor")
79
+ mt.add_command(bars, name="bars")
80
+ mt.add_command(doctor, name="dr")
81
+ mt.add_command(migrate)
82
+
83
+ loaded_plugins = load_plugins(mt)
84
+
85
+ logger.info("Plugins carregados: %s", loaded_plugins)
86
+
87
+
88
+ @mt.command(name="plugins")
89
+ def list_plugins():
90
+ """
91
+ Lista os plugins carregados.
92
+ """
93
+
94
+ if not loaded_plugins:
95
+ click.echo("Nenhum plugin carregado.")
96
+ return
97
+
98
+ click.echo("Plugins carregados:\n")
99
+
100
+ for name in loaded_plugins:
101
+ click.echo(f" {name}")
102
+
103
+
104
+ if __name__ == "__main__":
105
+ mt()
@@ -1,3 +1,15 @@
1
+ """
2
+ Core de acesso ao banco SQLite do mtcli.
3
+
4
+ Responsável por:
5
+
6
+ - Criar conexão SQLite
7
+ - Aplicar otimizações de performance
8
+ - Ativar WAL
9
+ - Gerenciar migrations
10
+ - Backup e manutenção do banco
11
+ """
12
+
1
13
  import sqlite3
2
14
  from pathlib import Path
3
15
  from datetime import datetime
@@ -7,6 +19,23 @@ BACKUP_DIR = Path.home() / ".mtcli" / "backups"
7
19
 
8
20
 
9
21
  def get_connection():
22
+ """
23
+ Cria ou retorna uma conexão SQLite otimizada para ingestão
24
+ contínua de ticks de mercado.
25
+
26
+ Configurações aplicadas:
27
+
28
+ - WAL (Write Ahead Logging)
29
+ - synchronous=NORMAL
30
+ - temp_store em memória
31
+ - mmap para leitura rápida
32
+ - cache expandido
33
+
34
+ Returns
35
+ -------
36
+ sqlite3.Connection
37
+ Conexão ativa com o banco SQLite.
38
+ """
10
39
 
11
40
  DB_PATH.parent.mkdir(parents=True, exist_ok=True)
12
41
  BACKUP_DIR.mkdir(parents=True, exist_ok=True)
@@ -16,8 +45,9 @@ def get_connection():
16
45
  conn.execute("PRAGMA journal_mode=WAL")
17
46
  conn.execute("PRAGMA synchronous=NORMAL")
18
47
  conn.execute("PRAGMA temp_store=MEMORY")
19
- conn.execute("PRAGMA mmap_size=30000000000")
48
+ conn.execute("PRAGMA mmap_size=268435456")
20
49
  conn.execute("PRAGMA cache_size=-200000")
50
+ conn.execute("PRAGMA journal_size_limit=67108864")
21
51
 
22
52
  conn.execute("""
23
53
  CREATE TABLE IF NOT EXISTS schema_migrations(
@@ -37,8 +67,12 @@ def get_connection():
37
67
 
38
68
  def wal_checkpoint(conn):
39
69
  """
40
- Reduz tamanho do WAL e mantém banco saudável.
70
+ Executa checkpoint do WAL.
71
+
72
+ Move os dados do arquivo `.wal` para o banco principal
73
+ e reduz seu tamanho.
41
74
  """
75
+
42
76
  conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
43
77
 
44
78
 
@@ -48,7 +82,10 @@ def wal_checkpoint(conn):
48
82
 
49
83
  def backup_database(conn):
50
84
  """
51
- Backup diário seguro do banco SQLite.
85
+ Realiza backup diário seguro do banco SQLite.
86
+
87
+ O backup utiliza a API nativa do SQLite,
88
+ permitindo cópia consistente mesmo com o banco em uso.
52
89
  """
53
90
 
54
91
  now = datetime.now().strftime("%Y%m%d")
@@ -0,0 +1,61 @@
1
+ """
2
+ Cache de ticks em memória.
3
+
4
+ Mantém uma janela recente de ticks para acesso rápido
5
+ sem necessidade de consultar o banco SQLite.
6
+ """
7
+
8
+ from collections import deque
9
+
10
+
11
+ class TickCache:
12
+ """
13
+ Mantém uma janela deslizante de ticks em memória.
14
+
15
+ Parameters
16
+ ----------
17
+ max_size : int
18
+ Número máximo de ticks armazenados no cache.
19
+ """
20
+
21
+ def __init__(self, max_size=10000):
22
+ self.buffer = deque(maxlen=max_size)
23
+
24
+ def add_many(self, ticks):
25
+ """
26
+ Adiciona múltiplos ticks ao cache.
27
+ """
28
+
29
+ for t in ticks:
30
+ self.buffer.append(t)
31
+
32
+ def add(self, tick):
33
+ """
34
+ Adiciona um único tick ao cache.
35
+ """
36
+
37
+ self.buffer.append(tick)
38
+
39
+ def get_all(self):
40
+ """
41
+ Retorna todos os ticks do cache.
42
+ """
43
+
44
+ return list(self.buffer)
45
+
46
+ def get_last(self):
47
+ """
48
+ Retorna o último tick armazenado.
49
+ """
50
+
51
+ if self.buffer:
52
+ return self.buffer[-1]
53
+
54
+ return None
55
+
56
+ def clear(self):
57
+ """
58
+ Limpa o cache.
59
+ """
60
+
61
+ self.buffer.clear()
@@ -0,0 +1,93 @@
1
+ """
2
+ Motor de captura de ticks multi-ativo.
3
+ """
4
+
5
+ import time
6
+ import threading
7
+ import MetaTrader5 as mt5
8
+ from datetime import datetime
9
+
10
+ from mtcli.mt5_context import mt5_conexao
11
+ from .tick_repository import TickRepository
12
+
13
+
14
+ class TickEngine:
15
+
16
+ POLL_INTERVAL = 0.2
17
+
18
+ def __init__(self, symbols):
19
+
20
+ self.symbols = symbols
21
+
22
+ self.repositories = {
23
+ symbol: TickRepository()
24
+ for symbol in symbols
25
+ }
26
+
27
+ self.running = False
28
+
29
+ def start(self):
30
+
31
+ thread = threading.Thread(
32
+ target=self._run,
33
+ daemon=True
34
+ )
35
+
36
+ self.running = True
37
+ thread.start()
38
+
39
+ def stop(self):
40
+
41
+ self.running = False
42
+
43
+ def _run(self):
44
+
45
+ with mt5_conexao():
46
+
47
+ last_positions = {}
48
+
49
+ for symbol in self.symbols:
50
+
51
+ repo = self.repositories[symbol]
52
+ last_msc = repo._get_last_tick_msc(symbol)
53
+
54
+ if last_msc:
55
+ last_positions[symbol] = last_msc + 1
56
+ else:
57
+ last_positions[symbol] = int(time.time() * 1000)
58
+
59
+ while self.running:
60
+
61
+ for symbol in self.symbols:
62
+
63
+ repo = self.repositories[symbol]
64
+
65
+ start = last_positions[symbol]
66
+
67
+ ticks = mt5.copy_ticks_from(
68
+ symbol,
69
+ datetime.fromtimestamp(start / 1000),
70
+ 1000,
71
+ mt5.COPY_TICKS_ALL
72
+ )
73
+
74
+ if ticks is None or len(ticks) == 0:
75
+ continue
76
+
77
+ repo.conn.execute("BEGIN")
78
+
79
+ try:
80
+
81
+ repo._insert_ticks(symbol, ticks)
82
+
83
+ repo.cache.add_many(ticks)
84
+
85
+ repo.conn.commit()
86
+
87
+ except Exception:
88
+
89
+ repo.conn.rollback()
90
+
91
+ last_positions[symbol] = int(ticks[-1]["time_msc"]) + 1
92
+
93
+ time.sleep(self.POLL_INTERVAL)
@@ -1,10 +1,11 @@
1
1
  """
2
- TickRepository profissional.
2
+ TickRepository.
3
3
 
4
4
  Responsável por:
5
5
 
6
6
  - Persistir ticks no SQLite
7
7
  - Sincronizar histórico inicial
8
+ - Consultas rápidas para engines (Renko etc)
8
9
  """
9
10
 
10
11
  import MetaTrader5 as mt5
@@ -32,6 +33,9 @@ class TickRepository:
32
33
  # ==========================================================
33
34
 
34
35
  def sync(self, symbol: str, days_back: int = 1):
36
+ """
37
+ Sincroniza histórico de ticks a partir do broker.
38
+ """
35
39
 
36
40
  total_inserted = 0
37
41
 
@@ -40,7 +44,7 @@ class TickRepository:
40
44
  last_msc = self._get_last_tick_msc(symbol)
41
45
 
42
46
  if last_msc:
43
- start = datetime.fromtimestamp(last_msc / 1000)
47
+ start = datetime.fromtimestamp((last_msc + 1) / 1000)
44
48
  else:
45
49
  start = datetime.now() - timedelta(days=days_back)
46
50
 
@@ -68,7 +72,7 @@ class TickRepository:
68
72
 
69
73
  last_msc = int(ticks[-1]["time_msc"])
70
74
 
71
- start = datetime.fromtimestamp(last_msc / 1000)
75
+ start = datetime.fromtimestamp((last_msc + 1) / 1000)
72
76
 
73
77
  if len(ticks) < self.BATCH_SIZE:
74
78
  break
@@ -106,23 +110,24 @@ class TickRepository:
106
110
 
107
111
  cursor.executemany(
108
112
  """
109
- INSERT OR IGNORE INTO ticks
110
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
113
+ INSERT INTO ticks(
114
+ symbol,time,time_msc,bid,ask,last,volume,flags
115
+ )
116
+ VALUES (?,?,?,?,?,?,?,?)
117
+ ON CONFLICT(symbol,time) DO NOTHING
111
118
  """,
112
119
  data,
113
120
  )
114
121
 
115
- inserted = cursor.rowcount
122
+ inserted = len(data)
116
123
 
117
124
  self.insert_counter += inserted
118
125
 
119
- # checkpoint periódico
120
126
  if self.insert_counter >= 200000:
121
127
 
122
128
  wal_checkpoint(self.conn)
123
129
  self.insert_counter = 0
124
130
 
125
- # backup diário
126
131
  today = datetime.now().date()
127
132
 
128
133
  if self.last_backup_day != today:
@@ -136,13 +141,34 @@ class TickRepository:
136
141
  # CONSULTAS
137
142
  # ==========================================================
138
143
 
144
+ def get_last_ticks(self, symbol, limit=5000):
145
+
146
+ cursor = self.conn.cursor()
147
+
148
+ cursor.execute(
149
+ """
150
+ SELECT time_msc,bid,ask,last,volume,flags
151
+ FROM ticks
152
+ WHERE symbol = ?
153
+ ORDER BY time_msc DESC
154
+ LIMIT ?
155
+ """,
156
+ (symbol, limit),
157
+ )
158
+
159
+ rows = cursor.fetchall()
160
+
161
+ rows.reverse()
162
+
163
+ return rows
164
+
139
165
  def get_ticks_between(self, symbol, start_msc, end_msc):
140
166
 
141
167
  cursor = self.conn.cursor()
142
168
 
143
169
  cursor.execute(
144
170
  """
145
- SELECT time_msc, bid, ask, last, volume, flags
171
+ SELECT time_msc,bid,ask,last,volume,flags
146
172
  FROM ticks
147
173
  WHERE symbol = ?
148
174
  AND time_msc BETWEEN ? AND ?
@@ -26,7 +26,7 @@ class TickStreamer:
26
26
 
27
27
  def start(self):
28
28
  """
29
- Inicia captura contínua.
29
+ Inicia captura contínua de ticks.
30
30
  """
31
31
 
32
32
  self.running = True
@@ -36,7 +36,7 @@ class TickStreamer:
36
36
  last_msc = self.repo._get_last_tick_msc(self.symbol)
37
37
 
38
38
  if last_msc:
39
- start = last_msc
39
+ start = last_msc + 1
40
40
  else:
41
41
  start = int(time.time() * 1000)
42
42
 
@@ -51,7 +51,7 @@ class TickStreamer:
51
51
 
52
52
  if ticks is None or len(ticks) == 0:
53
53
 
54
- time.sleep(0.05)
54
+ time.sleep(0.1)
55
55
  continue
56
56
 
57
57
  self.repo.conn.execute("BEGIN")
@@ -68,7 +68,7 @@ class TickStreamer:
68
68
 
69
69
  self.repo.conn.rollback()
70
70
 
71
- start = int(ticks[-1]["time_msc"])
71
+ start = int(ticks[-1]["time_msc"]) + 1
72
72
 
73
73
  def stop(self):
74
74
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.8.0.dev0"
3
+ version = "3.8.0.dev2"
4
4
  description = "Aplicativo CLI para exibir gráficos do MetaTrader 5 screen reader friendly"
5
5
  authors = [
6
6
  {name = "Valmir França",email = "vfranca3@gmail.com"}
@@ -1,135 +0,0 @@
1
- """
2
- CLI principal do mtcli.
3
-
4
- Este módulo define o grupo principal `mt`
5
- e inicializa o carregamento de plugins.
6
-
7
- Também inicia a captura contínua de ticks para manter
8
- um histórico próprio independente do broker.
9
- """
10
-
11
- import os
12
- import threading
13
- import click
14
-
15
- from mtcli.plugin_loader import load_plugins
16
- from mtcli.logger import setup_logger
17
- from mtcli.marketdata.tick_streamer import TickStreamer
18
-
19
- from .commands.bars import bars
20
- from .commands.doctor import doctor
21
- from .commands.migrate import migrate
22
-
23
-
24
- logger = setup_logger(__name__)
25
-
26
-
27
- # ---------------------------------------------------------
28
- # Configuração de captura de ticks
29
- # ---------------------------------------------------------
30
-
31
- _tick_streamer = None
32
-
33
-
34
- def start_tick_capture():
35
- """
36
- Inicia captura contínua de ticks em background.
37
-
38
- O símbolo pode ser configurado via variável de ambiente:
39
-
40
- MTCLI_SYMBOL=WINJ26
41
- """
42
-
43
- global _tick_streamer
44
-
45
- if _tick_streamer:
46
- return
47
-
48
- symbol = os.getenv("SYMBOL")
49
-
50
- if not symbol:
51
- logger.info("Captura de ticks desativada (MTCLI_SYMBOL não definido).")
52
- return
53
-
54
- logger.info("Iniciando captura contínua de ticks para %s", symbol)
55
-
56
- _tick_streamer = TickStreamer(symbol)
57
-
58
- thread = threading.Thread(
59
- target=_tick_streamer.start,
60
- daemon=True,
61
- name="mtcli-tick-streamer",
62
- )
63
-
64
- thread.start()
65
-
66
- logger.info("Captura de ticks iniciada em background.")
67
-
68
-
69
- # ---------------------------------------------------------
70
- # CLI principal
71
- # ---------------------------------------------------------
72
-
73
- @click.group(context_settings={"max_content_width": 120}, invoke_without_command=True)
74
- @click.version_option(package_name="mtcli")
75
- @click.pass_context
76
- def mt(ctx):
77
- """
78
- CLI principal do mtcli.
79
-
80
- Exibe gráficos e informações de mercado
81
- em formato textual compatível com leitores de tela.
82
- """
83
-
84
- # inicia captura de ticks
85
- start_tick_capture()
86
-
87
- if ctx.invoked_subcommand is None:
88
- click.echo(ctx.get_help())
89
-
90
-
91
- # ---------------------------------------------------------
92
- # Comandos internos
93
- # ---------------------------------------------------------
94
-
95
- mt.add_command(doctor, name="doctor")
96
- mt.add_command(bars, name="bars")
97
- mt.add_command(doctor, name="dr")
98
- mt.add_command(migrate)
99
-
100
-
101
- # ---------------------------------------------------------
102
- # Carregamento de plugins
103
- # ---------------------------------------------------------
104
-
105
- loaded_plugins = load_plugins(mt)
106
-
107
- logger.info("Plugins carregados: %s", loaded_plugins)
108
-
109
-
110
- # ---------------------------------------------------------
111
- # Comando utilitário: listar plugins
112
- # ---------------------------------------------------------
113
-
114
- @mt.command(name="plugins")
115
- def list_plugins():
116
- """
117
- Lista os plugins atualmente carregados no mtcli.
118
- """
119
-
120
- if not loaded_plugins:
121
- click.echo("Nenhum plugin carregado.")
122
- return
123
-
124
- click.echo("Plugins carregados:\n")
125
-
126
- for name in loaded_plugins:
127
- click.echo(f" {name}")
128
-
129
-
130
- # ---------------------------------------------------------
131
- # Entry point
132
- # ---------------------------------------------------------
133
-
134
- if __name__ == "__main__":
135
- mt()
@@ -1,38 +0,0 @@
1
- """
2
- Cache de ticks em memória.
3
- """
4
-
5
- from collections import deque
6
-
7
-
8
- class TickCache:
9
- """
10
- Mantém janela recente de ticks em memória.
11
- """
12
-
13
- def __init__(self, max_size=10000):
14
- self.buffer = deque(maxlen=max_size)
15
-
16
- def add_many(self, ticks):
17
-
18
- for t in ticks:
19
- self.buffer.append(t)
20
-
21
- def add(self, tick):
22
-
23
- self.buffer.append(tick)
24
-
25
- def get_all(self):
26
-
27
- return list(self.buffer)
28
-
29
- def get_last(self):
30
-
31
- if self.buffer:
32
- return self.buffer[-1]
33
-
34
- return None
35
-
36
- def clear(self):
37
-
38
- self.buffer.clear()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes