mtcli 3.8.0.dev4__tar.gz → 3.8.0.dev6__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 (83) hide show
  1. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/PKG-INFO +1 -1
  2. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/cli.py +41 -16
  3. mtcli-3.8.0.dev6/mtcli/commands/ticks.py +47 -0
  4. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/database.py +1 -0
  5. mtcli-3.8.0.dev6/mtcli/marketdata/tick_engine.py +134 -0
  6. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/marketdata/tick_repository.py +39 -38
  7. mtcli-3.8.0.dev6/mtcli/migrations/004_ticks_pk_time_msc.py +56 -0
  8. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/pyproject.toml +1 -1
  9. mtcli-3.8.0.dev4/mtcli/marketdata/tick_engine.py +0 -93
  10. mtcli-3.8.0.dev4/mtcli/marketdata/tick_streamer.py +0 -62
  11. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/LICENSE +0 -0
  12. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/README.md +0 -0
  13. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/__init__.py +0 -0
  14. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/commands/__init__.py +0 -0
  15. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/commands/bars.py +0 -0
  16. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/commands/conf.py +0 -0
  17. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/commands/doctor.py +0 -0
  18. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/commands/migrate.py +0 -0
  19. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/conecta.py +0 -0
  20. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/conf.py +0 -0
  21. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/config_registre.py +0 -0
  22. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/data/__init__.py +0 -0
  23. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/data/base.py +0 -0
  24. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/data/csv.py +0 -0
  25. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/data/mt5.py +0 -0
  26. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/domain/__init__.py +0 -0
  27. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/domain/timeframe.py +0 -0
  28. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/logger.py +0 -0
  29. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/marketdata/__init__.py +0 -0
  30. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/marketdata/tick_cache.py +0 -0
  31. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/migrations/001_initial_schema.py +0 -0
  32. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/migrations/002_ticks_time_msc.py +0 -0
  33. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
  34. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/migrations/__init__.py +0 -0
  35. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/migrations/runner.py +0 -0
  36. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/__init__.py +0 -0
  37. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/bar_model.py +0 -0
  38. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/bars_model.py +0 -0
  39. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/chart_model.py +0 -0
  40. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/conf_model.py +0 -0
  41. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/consecutive_bars_model.py +0 -0
  42. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/rates_model.py +0 -0
  43. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/signals_model.py +0 -0
  44. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/models/unconsecutive_bar_model.py +0 -0
  45. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/mt5_context.py +0 -0
  46. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugin.py +0 -0
  47. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugin_loader.py +0 -0
  48. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugin_manager.py +0 -0
  49. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/__init__.py +0 -0
  50. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/exemplo.py-dist +0 -0
  51. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/__init__.py +0 -0
  52. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/cli.py +0 -0
  53. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/conf.py +0 -0
  54. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  55. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  56. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  57. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  58. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  59. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/__init__.py +0 -0
  60. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/cli.py +0 -0
  61. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/conf.py +0 -0
  62. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  63. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  64. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  65. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  66. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/__init__.py +0 -0
  67. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/cli.py +0 -0
  68. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/conf.py +0 -0
  69. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  70. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  71. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  72. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  73. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/__init__.py +0 -0
  74. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/close_view.py +0 -0
  75. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/full_view.py +0 -0
  76. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/high_view.py +0 -0
  77. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/low_view.py +0 -0
  78. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/min_view.py +0 -0
  79. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/open_view.py +0 -0
  80. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/ranges_view.py +0 -0
  81. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/rates_view.py +0 -0
  82. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/mtcli/views/vars_view.py +0 -0
  83. {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev6}/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.dev4
3
+ Version: 3.8.0.dev6
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
@@ -1,56 +1,74 @@
1
1
  """
2
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.
3
19
  """
4
20
 
5
21
  import os
6
- import threading
7
22
  import click
8
23
 
9
24
  from mtcli.plugin_loader import load_plugins
10
25
  from mtcli.logger import setup_logger
11
- from mtcli.marketdata.tick_streamer import TickStreamer
26
+ from mtcli.marketdata.tick_engine import TickEngine
12
27
 
13
28
  from .commands.bars import bars
14
29
  from .commands.doctor import doctor
15
30
  from .commands.migrate import migrate
31
+ from .commands.ticks import ticks
16
32
 
17
33
 
18
34
  logger = setup_logger(__name__)
19
35
 
20
- _tick_streamer = None
36
+ _tick_engine = None
21
37
 
22
38
 
23
39
  def start_tick_capture():
40
+ """
41
+ Inicia captura contínua de ticks em background.
24
42
 
25
- global _tick_streamer
43
+ Se a variável de ambiente MTCLI_SYMBOL estiver definida,
44
+ o mtcli iniciará automaticamente um TickEngine para
45
+ coletar ticks continuamente.
26
46
 
27
- if _tick_streamer:
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:
28
54
  return
29
55
 
30
56
  symbol = os.getenv("MTCLI_SYMBOL")
31
57
 
32
58
  if not symbol:
33
- logger.info("Captura de ticks desativada (MTCLI_SYMBOL não definido).")
59
+ logger.info("Captura automática de ticks desativada (MTCLI_SYMBOL não definido).")
34
60
  return
35
61
 
36
62
  logger.info("Iniciando captura contínua de ticks para %s", symbol)
37
63
 
38
64
  try:
39
65
 
40
- _tick_streamer = TickStreamer(symbol)
41
-
42
- thread = threading.Thread(
43
- target=_tick_streamer.start,
44
- daemon=True,
45
- name="mtcli-tick-streamer",
46
- )
47
-
48
- thread.start()
66
+ _tick_engine = TickEngine([symbol])
67
+ _tick_engine.start()
49
68
 
50
69
  logger.info("Captura de ticks iniciada em background.")
51
70
 
52
71
  except Exception:
53
-
54
72
  logger.exception("Falha ao iniciar captura de ticks")
55
73
 
56
74
 
@@ -58,6 +76,9 @@ def start_tick_capture():
58
76
  @click.version_option(package_name="mtcli")
59
77
  @click.pass_context
60
78
  def mt(ctx):
79
+ """
80
+ CLI principal do mtcli.
81
+ """
61
82
 
62
83
  start_tick_capture()
63
84
 
@@ -69,6 +90,7 @@ mt.add_command(doctor, name="doctor")
69
90
  mt.add_command(bars, name="bars")
70
91
  mt.add_command(doctor, name="dr")
71
92
  mt.add_command(migrate)
93
+ mt.add_command(ticks)
72
94
 
73
95
  loaded_plugins = load_plugins(mt)
74
96
 
@@ -77,6 +99,9 @@ logger.info("Plugins carregados: %s", loaded_plugins)
77
99
 
78
100
  @mt.command(name="plugins")
79
101
  def list_plugins():
102
+ """
103
+ Lista os plugins carregados no mtcli.
104
+ """
80
105
 
81
106
  if not loaded_plugins:
82
107
  click.echo("Nenhum plugin carregado.")
@@ -0,0 +1,47 @@
1
+ """
2
+ Comando CLI para captura contínua de ticks.
3
+
4
+ Permite iniciar um TickEngine manualmente
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
+ A captura continua até o usuário interromper
13
+ com Ctrl+C.
14
+ """
15
+
16
+ import time
17
+ import click
18
+
19
+ from mtcli.marketdata.tick_engine import TickEngine
20
+
21
+
22
+ @click.command()
23
+ @click.argument("symbols", nargs=-1)
24
+ def ticks(symbols):
25
+ """
26
+ Inicia captura contínua de ticks para os símbolos informados.
27
+ """
28
+
29
+ if not symbols:
30
+ click.echo("Informe ao menos um símbolo.")
31
+ return
32
+
33
+ engine = TickEngine(symbols)
34
+
35
+ click.echo("Iniciando captura de ticks...")
36
+
37
+ engine.start()
38
+
39
+ try:
40
+
41
+ while True:
42
+ time.sleep(1)
43
+
44
+ except KeyboardInterrupt:
45
+
46
+ click.echo("\nEncerrando captura de ticks...")
47
+ engine.stop()
@@ -44,6 +44,7 @@ def get_connection():
44
44
  conn.execute("PRAGMA mmap_size=268435456")
45
45
  conn.execute("PRAGMA cache_size=-200000")
46
46
  conn.execute("PRAGMA journal_size_limit=67108864")
47
+ conn.execute("PRAGMA read_uncommitted = TRUE")
47
48
 
48
49
  # checkpoint automático
49
50
  conn.execute("PRAGMA wal_autocheckpoint=1000")
@@ -0,0 +1,134 @@
1
+ """
2
+ TickEngine - motor de captura contínua de ticks.
3
+
4
+ Responsável por:
5
+
6
+ - sincronizar histórico inicial
7
+ - capturar ticks em tempo real
8
+ - persistir ticks via TickRepository
9
+ """
10
+
11
+ import time
12
+ import threading
13
+ import MetaTrader5 as mt5
14
+
15
+ from datetime import datetime
16
+
17
+ from mtcli.mt5_context import mt5_conexao
18
+ from .tick_repository import TickRepository
19
+
20
+
21
+ class TickEngine:
22
+
23
+ POLL_INTERVAL = 0.2
24
+ BATCH_SIZE = 1000
25
+ OVERLAP_MS = 5
26
+
27
+ def __init__(self, symbols):
28
+
29
+ self.symbols = symbols
30
+
31
+ self.repositories = {
32
+ symbol: TickRepository()
33
+ for symbol in symbols
34
+ }
35
+
36
+ self.running = False
37
+ self.thread = None
38
+
39
+ def start(self):
40
+
41
+ if self.running:
42
+ return
43
+
44
+ self.running = True
45
+
46
+ self.thread = threading.Thread(
47
+ target=self._run,
48
+ daemon=True,
49
+ name="mtcli-tick-engine",
50
+ )
51
+
52
+ self.thread.start()
53
+
54
+ def stop(self):
55
+
56
+ self.running = False
57
+
58
+ if self.thread:
59
+ self.thread.join()
60
+
61
+ def _run(self):
62
+
63
+ with mt5_conexao():
64
+
65
+ last_positions = {}
66
+
67
+ for symbol in self.symbols:
68
+
69
+ repo = self.repositories[symbol]
70
+
71
+ # sincroniza histórico
72
+ repo.sync(symbol)
73
+
74
+ last_msc = repo._get_last_tick_msc(symbol)
75
+
76
+ if last_msc:
77
+ last_positions[symbol] = last_msc
78
+ else:
79
+ last_positions[symbol] = int(time.time() * 1000)
80
+
81
+ while self.running:
82
+
83
+ for symbol in self.symbols:
84
+ self._drain_symbol(symbol, last_positions)
85
+
86
+ time.sleep(self.POLL_INTERVAL)
87
+
88
+ def _drain_symbol(self, symbol, last_positions):
89
+
90
+ repo = self.repositories[symbol]
91
+
92
+ last_msc = last_positions[symbol]
93
+
94
+ start_dt = datetime.fromtimestamp(
95
+ (last_msc - self.OVERLAP_MS) / 1000
96
+ )
97
+
98
+ while True:
99
+
100
+ ticks = mt5.copy_ticks_from(
101
+ symbol,
102
+ start_dt,
103
+ self.BATCH_SIZE,
104
+ mt5.COPY_TICKS_ALL,
105
+ )
106
+
107
+ if ticks is None or len(ticks) == 0:
108
+ break
109
+
110
+ repo.conn.execute("BEGIN")
111
+
112
+ try:
113
+
114
+ repo._insert_ticks(symbol, ticks)
115
+
116
+ repo.cache.add_many(ticks)
117
+
118
+ repo.conn.commit()
119
+
120
+ except Exception:
121
+
122
+ repo.conn.rollback()
123
+ raise
124
+
125
+ last_msc = int(ticks[-1]["time_msc"])
126
+
127
+ last_positions[symbol] = last_msc + 1
128
+
129
+ start_dt = datetime.fromtimestamp(
130
+ (last_msc - self.OVERLAP_MS) / 1000
131
+ )
132
+
133
+ if len(ticks) < self.BATCH_SIZE:
134
+ break
@@ -3,12 +3,13 @@ TickRepository.
3
3
 
4
4
  Responsável por:
5
5
 
6
- - Persistir ticks no SQLite
7
- - Sincronizar histórico inicial
8
- - Consultas rápidas para engines
6
+ - persistência de ticks
7
+ - sincronização histórica
8
+ - consultas rápidas
9
9
  """
10
10
 
11
11
  import MetaTrader5 as mt5
12
+
12
13
  from datetime import datetime, timedelta
13
14
 
14
15
  from ..database import get_connection, backup_database
@@ -18,7 +19,7 @@ from mtcli.mt5_context import mt5_conexao
18
19
 
19
20
  class TickRepository:
20
21
 
21
- BATCH_SIZE = 200000
22
+ RANGE_WINDOW_MINUTES = 10
22
23
 
23
24
  def __init__(self):
24
25
 
@@ -28,64 +29,64 @@ class TickRepository:
28
29
  self.last_backup_day = None
29
30
 
30
31
  # ==========================================================
31
- # SINCRONIZAÇÃO HISTÓRICA
32
+ # SYNC HISTÓRICO
32
33
  # ==========================================================
33
34
 
34
35
  def sync(self, symbol: str, days_back: int = 1):
35
36
 
36
37
  total_inserted = 0
37
38
 
38
- with mt5_conexao():
39
+ end = datetime.now()
39
40
 
40
- last_msc = self._get_last_tick_msc(symbol)
41
+ last_msc = self._get_last_tick_msc(symbol)
41
42
 
42
- if last_msc:
43
- start = datetime.fromtimestamp((last_msc + 1) / 1000)
44
- else:
45
- start = datetime.now() - timedelta(days=days_back)
43
+ if last_msc:
44
+ start = datetime.fromtimestamp((last_msc + 1) / 1000)
45
+ else:
46
+ start = end - timedelta(days=days_back)
46
47
 
47
- self.conn.execute("BEGIN")
48
+ window = timedelta(minutes=self.RANGE_WINDOW_MINUTES)
49
+
50
+ with mt5_conexao():
48
51
 
49
- try:
52
+ while start < end:
50
53
 
51
- while True:
54
+ chunk_end = min(start + window, end)
52
55
 
53
- ticks = mt5.copy_ticks_from(
54
- symbol,
55
- start,
56
- self.BATCH_SIZE,
57
- mt5.COPY_TICKS_ALL,
58
- )
56
+ ticks = mt5.copy_ticks_range(
57
+ symbol,
58
+ start,
59
+ chunk_end,
60
+ mt5.COPY_TICKS_ALL
61
+ )
59
62
 
60
- if ticks is None or len(ticks) == 0:
61
- break
63
+ if ticks is not None and len(ticks) > 0:
62
64
 
63
- inserted = self._insert_ticks(symbol, ticks)
65
+ self.conn.execute("BEGIN")
64
66
 
65
- total_inserted += inserted
67
+ try:
66
68
 
67
- self.cache.add_many(ticks)
69
+ inserted = self._insert_ticks(symbol, ticks)
68
70
 
69
- last_msc = int(ticks[-1]["time_msc"])
71
+ total_inserted += inserted
70
72
 
71
- start = datetime.fromtimestamp((last_msc + 1) / 1000)
73
+ self.cache.add_many(ticks)
72
74
 
73
- if len(ticks) < self.BATCH_SIZE:
74
- break
75
+ self.conn.commit()
75
76
 
76
- self.conn.commit()
77
+ except Exception:
77
78
 
78
- except Exception:
79
+ self.conn.rollback()
80
+ raise
79
81
 
80
- self.conn.rollback()
81
- raise
82
+ start = chunk_end
82
83
 
83
84
  self._daily_backup()
84
85
 
85
86
  return total_inserted
86
87
 
87
88
  # ==========================================================
88
- # INSERÇÃO
89
+ # INSERT
89
90
  # ==========================================================
90
91
 
91
92
  def _insert_ticks(self, symbol, ticks):
@@ -112,7 +113,7 @@ class TickRepository:
112
113
  symbol,time,time_msc,bid,ask,last,volume,flags
113
114
  )
114
115
  VALUES (?,?,?,?,?,?,?,?)
115
- ON CONFLICT(symbol,time) DO NOTHING
116
+ ON CONFLICT(symbol,time_msc) DO NOTHING
116
117
  """,
117
118
  data,
118
119
  )
@@ -162,7 +163,7 @@ class TickRepository:
162
163
  return cursor.fetchall()
163
164
 
164
165
  # ==========================================================
165
- # UTILITÁRIOS
166
+ # UTIL
166
167
  # ==========================================================
167
168
 
168
169
  def _get_last_tick_msc(self, symbol):
@@ -178,9 +179,9 @@ class TickRepository:
178
179
  (symbol,),
179
180
  )
180
181
 
181
- result = cursor.fetchone()
182
+ row = cursor.fetchone()
182
183
 
183
- return result[0] if result and result[0] else None
184
+ return row[0] if row and row[0] else None
184
185
 
185
186
  def _daily_backup(self):
186
187
 
@@ -0,0 +1,56 @@
1
+ def upgrade(conn):
2
+
3
+ cursor = conn.execute("""
4
+ SELECT sql
5
+ FROM sqlite_master
6
+ WHERE type='table'
7
+ AND name='ticks'
8
+ """)
9
+
10
+ row = cursor.fetchone()
11
+
12
+ if not row:
13
+ return
14
+
15
+ if "PRIMARY KEY(symbol, time_msc)" in row[0]:
16
+ return
17
+
18
+ print("Rebuilding ticks table with PK(symbol,time_msc)...")
19
+
20
+ conn.execute("PRAGMA foreign_keys=OFF")
21
+
22
+ conn.execute("""
23
+ CREATE TABLE ticks_new(
24
+ symbol TEXT NOT NULL,
25
+ time INTEGER NOT NULL,
26
+ time_msc INTEGER NOT NULL,
27
+ bid REAL,
28
+ ask REAL,
29
+ last REAL,
30
+ volume REAL,
31
+ flags INTEGER,
32
+ PRIMARY KEY(symbol, time_msc)
33
+ ) WITHOUT ROWID
34
+ """)
35
+
36
+ conn.execute("""
37
+ INSERT INTO ticks_new
38
+ SELECT
39
+ symbol,
40
+ time,
41
+ COALESCE(time_msc,time),
42
+ bid,
43
+ ask,
44
+ last,
45
+ volume,
46
+ flags
47
+ FROM ticks
48
+ """)
49
+
50
+ conn.execute("DROP TABLE ticks")
51
+
52
+ conn.execute("ALTER TABLE ticks_new RENAME TO ticks")
53
+
54
+ conn.commit()
55
+
56
+ print("ticks table rebuilt with PK(symbol,time_msc)")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.8.0.dev4"
3
+ version = "3.8.0.dev6"
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,93 +0,0 @@
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,62 +0,0 @@
1
- """
2
- Captura contínua de ticks.
3
- """
4
-
5
- import time
6
- import MetaTrader5 as mt5
7
- from datetime import datetime
8
-
9
- from mtcli.mt5_context import mt5_conexao
10
- from .tick_repository import TickRepository
11
-
12
-
13
- class TickStreamer:
14
-
15
- def __init__(self, symbol):
16
-
17
- self.symbol = symbol
18
- self.repo = TickRepository()
19
- self.running = False
20
-
21
- def start(self):
22
-
23
- self.running = True
24
-
25
- with mt5_conexao():
26
-
27
- last_msc = self.repo._get_last_tick_msc(self.symbol)
28
-
29
- if last_msc:
30
- start = last_msc + 1
31
- else:
32
- start = int(time.time() * 1000)
33
-
34
- while self.running:
35
-
36
- ticks = mt5.copy_ticks_from(
37
- self.symbol,
38
- datetime.fromtimestamp(start / 1000),
39
- 1000,
40
- mt5.COPY_TICKS_ALL,
41
- )
42
-
43
- if ticks is None or len(ticks) == 0:
44
-
45
- time.sleep(0.1)
46
- continue
47
-
48
- self.repo.conn.execute("BEGIN")
49
-
50
- try:
51
-
52
- self.repo._insert_ticks(self.symbol, ticks)
53
-
54
- self.repo.cache.add_many(ticks)
55
-
56
- self.repo.conn.commit()
57
-
58
- except Exception:
59
-
60
- self.repo.conn.rollback()
61
-
62
- start = int(ticks[-1]["time_msc"]) + 1
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