mtcli 3.8.0.dev9__tar.gz → 3.8.0.dev11__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 (89) hide show
  1. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/PKG-INFO +1 -1
  2. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/marketdata/tick_engine.py +50 -22
  3. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/marketdata/tick_repository.py +3 -3
  4. mtcli-3.8.0.dev11/mtcli/marketdata/tick_writer.py +98 -0
  5. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/pyproject.toml +1 -1
  6. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/LICENSE +0 -0
  7. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/README.md +0 -0
  8. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/__init__.py +0 -0
  9. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/__main__.py +0 -0
  10. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/cli.py +0 -0
  11. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/cli_dev.py +0 -0
  12. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands/__init__.py +0 -0
  13. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands/bars.py +0 -0
  14. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands/conf.py +0 -0
  15. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands/doctor.py +0 -0
  16. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands/ticks.py +0 -0
  17. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands_dev/__init__.py +0 -0
  18. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/commands_dev/migrate.py +0 -0
  19. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/conecta.py +0 -0
  20. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/conf.py +0 -0
  21. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/config_registre.py +0 -0
  22. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/data/__init__.py +0 -0
  23. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/data/base.py +0 -0
  24. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/data/csv.py +0 -0
  25. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/data/mt5.py +0 -0
  26. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/database.py +0 -0
  27. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/domain/__init__.py +0 -0
  28. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/domain/timeframe.py +0 -0
  29. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/logger.py +0 -0
  30. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/marketdata/__init__.py +0 -0
  31. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/marketdata/tick_cache.py +0 -0
  32. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/001_initial_schema.py +0 -0
  33. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/002_ticks_time_msc.py +0 -0
  34. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
  35. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/004_ticks_pk_time_msc.py +0 -0
  36. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/005_tick_price_compression.py +0 -0
  37. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/006_add_index_ticks_symbol_time.py +0 -0
  38. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +0 -0
  39. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/__init__.py +0 -0
  40. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/__main__.py +0 -0
  41. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/migrations/runner.py +0 -0
  42. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/__init__.py +0 -0
  43. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/bar_model.py +0 -0
  44. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/bars_model.py +0 -0
  45. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/chart_model.py +0 -0
  46. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/conf_model.py +0 -0
  47. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/consecutive_bars_model.py +0 -0
  48. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/rates_model.py +0 -0
  49. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/signals_model.py +0 -0
  50. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/models/unconsecutive_bar_model.py +0 -0
  51. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/mt5_context.py +0 -0
  52. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugin.py +0 -0
  53. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugin_loader.py +0 -0
  54. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugin_manager.py +0 -0
  55. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/__init__.py +0 -0
  56. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/exemplo.py-dist +0 -0
  57. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/__init__.py +0 -0
  58. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/cli.py +0 -0
  59. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/conf.py +0 -0
  60. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  61. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  62. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  63. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  64. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  65. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/__init__.py +0 -0
  66. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/cli.py +0 -0
  67. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/conf.py +0 -0
  68. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  69. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  70. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  71. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  72. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/__init__.py +0 -0
  73. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/cli.py +0 -0
  74. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/conf.py +0 -0
  75. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  76. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  77. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  78. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  79. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/__init__.py +0 -0
  80. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/close_view.py +0 -0
  81. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/full_view.py +0 -0
  82. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/high_view.py +0 -0
  83. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/low_view.py +0 -0
  84. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/min_view.py +0 -0
  85. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/open_view.py +0 -0
  86. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/ranges_view.py +0 -0
  87. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/rates_view.py +0 -0
  88. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/mtcli/views/vars_view.py +0 -0
  89. {mtcli-3.8.0.dev9 → mtcli-3.8.0.dev11}/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.dev9
3
+ Version: 3.8.0.dev11
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
@@ -5,7 +5,8 @@ Responsável por:
5
5
 
6
6
  - sincronizar histórico inicial
7
7
  - capturar ticks em tempo real
8
- - persistir ticks via TickRepository
8
+ - enviar ticks para o TickWriter
9
+ - manter posição de leitura por símbolo
9
10
  """
10
11
 
11
12
  import time
@@ -16,33 +17,53 @@ from datetime import datetime
16
17
 
17
18
  from mtcli.mt5_context import mt5_conexao
18
19
  from .tick_repository import TickRepository
20
+ from .tick_writer import TickWriter
19
21
 
20
22
 
21
23
  class TickEngine:
22
24
 
23
- POLL_INTERVAL = 0.2
25
+ # intervalo entre ciclos de polling
26
+ POLL_INTERVAL = 0.05
27
+
28
+ # tamanho máximo retornado pelo MT5
24
29
  BATCH_SIZE = 1000
25
- OVERLAP_MS = 5
30
+
31
+ # sobreposição para evitar perda de ticks
32
+ OVERLAP_MS = 20
26
33
 
27
34
  def __init__(self, symbols):
28
35
 
29
36
  self.symbols = symbols
30
37
 
38
+ # repositórios por símbolo
31
39
  self.repositories = {
32
40
  symbol: TickRepository()
33
41
  for symbol in symbols
34
42
  }
35
43
 
44
+ # writer assíncrono
45
+ self.writer = TickWriter(self.repositories)
46
+
36
47
  self.running = False
37
48
  self.thread = None
38
49
 
50
+ # ==========================================================
51
+ # CONTROLE DO ENGINE
52
+ # ==========================================================
53
+
39
54
  def start(self):
55
+ """
56
+ Inicia o motor de captura.
57
+ """
40
58
 
41
59
  if self.running:
42
60
  return
43
61
 
44
62
  self.running = True
45
63
 
64
+ # inicia writer
65
+ self.writer.start()
66
+
46
67
  self.thread = threading.Thread(
47
68
  target=self._run,
48
69
  daemon=True,
@@ -52,23 +73,36 @@ class TickEngine:
52
73
  self.thread.start()
53
74
 
54
75
  def stop(self):
76
+ """
77
+ Interrompe captura de ticks.
78
+ """
55
79
 
56
80
  self.running = False
57
81
 
58
82
  if self.thread:
59
83
  self.thread.join()
60
84
 
85
+ # garante flush final
86
+ self.writer.stop()
87
+
88
+ # ==========================================================
89
+ # LOOP PRINCIPAL
90
+ # ==========================================================
91
+
61
92
  def _run(self):
62
93
 
63
94
  with mt5_conexao():
64
95
 
65
96
  last_positions = {}
66
97
 
98
+ # --------------------------------------------------
99
+ # sincronização inicial
100
+ # --------------------------------------------------
101
+
67
102
  for symbol in self.symbols:
68
103
 
69
104
  repo = self.repositories[symbol]
70
105
 
71
- # sincroniza histórico
72
106
  repo.sync(symbol)
73
107
 
74
108
  last_msc = repo._get_last_tick_msc(symbol)
@@ -78,6 +112,10 @@ class TickEngine:
78
112
  else:
79
113
  last_positions[symbol] = int(time.time() * 1000)
80
114
 
115
+ # --------------------------------------------------
116
+ # loop contínuo
117
+ # --------------------------------------------------
118
+
81
119
  while self.running:
82
120
 
83
121
  for symbol in self.symbols:
@@ -85,14 +123,16 @@ class TickEngine:
85
123
 
86
124
  time.sleep(self.POLL_INTERVAL)
87
125
 
88
- def _drain_symbol(self, symbol, last_positions):
126
+ # ==========================================================
127
+ # CAPTURA POR SÍMBOLO
128
+ # ==========================================================
89
129
 
90
- repo = self.repositories[symbol]
130
+ def _drain_symbol(self, symbol, last_positions):
91
131
 
92
132
  last_msc = last_positions[symbol]
93
133
 
94
134
  start_dt = datetime.fromtimestamp(
95
- (last_msc - self.OVERLAP_MS) / 1000
135
+ (last_msc - self.OVERLAP_MS) * 0.001
96
136
  )
97
137
 
98
138
  while True:
@@ -107,27 +147,15 @@ class TickEngine:
107
147
  if ticks is None or len(ticks) == 0:
108
148
  break
109
149
 
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
150
+ # envia ticks para o writer assíncrono
151
+ self.writer.push(symbol, ticks)
124
152
 
125
153
  last_msc = int(ticks[-1]["time_msc"])
126
154
 
127
155
  last_positions[symbol] = last_msc + 1
128
156
 
129
157
  start_dt = datetime.fromtimestamp(
130
- (last_msc - self.OVERLAP_MS) / 1000
158
+ (last_msc - self.OVERLAP_MS) * 0.001
131
159
  )
132
160
 
133
161
  if len(ticks) < self.BATCH_SIZE:
@@ -45,7 +45,7 @@ class TickRepository:
45
45
  last_msc = self._get_last_tick_msc(symbol)
46
46
 
47
47
  if last_msc:
48
- start = datetime.fromtimestamp((last_msc + 1) / 1000)
48
+ start = datetime.fromtimestamp((last_msc + 1) * 0.001)
49
49
  else:
50
50
  start = end - timedelta(days=days_back)
51
51
 
@@ -99,7 +99,7 @@ class TickRepository:
99
99
 
100
100
  scale = self.PRICE_SCALE
101
101
 
102
- data = [
102
+ data = (
103
103
  (
104
104
  symbol,
105
105
  int(t["time_msc"]),
@@ -110,7 +110,7 @@ class TickRepository:
110
110
  int(t["flags"]),
111
111
  )
112
112
  for t in ticks
113
- ]
113
+ )
114
114
 
115
115
  cursor.executemany(
116
116
  """
@@ -0,0 +1,98 @@
1
+ """
2
+ TickWriter.
3
+
4
+ Thread dedicada para escrita de ticks no SQLite.
5
+ Recebe ticks via Queue e grava em batches grandes
6
+ para maximizar performance.
7
+ """
8
+
9
+ import threading
10
+ from queue import Queue, Empty
11
+ from time import sleep
12
+
13
+
14
+ class TickWriter:
15
+
16
+ FLUSH_INTERVAL = 0.2
17
+ MAX_BATCH = 5000
18
+
19
+ def __init__(self, repositories):
20
+
21
+ self.repositories = repositories
22
+ self.queue = Queue(maxsize=100000)
23
+
24
+ self.running = False
25
+ self.thread = None
26
+
27
+ def start(self):
28
+
29
+ if self.running:
30
+ return
31
+
32
+ self.running = True
33
+
34
+ self.thread = threading.Thread(
35
+ target=self._run,
36
+ daemon=True,
37
+ name="mtcli-tick-writer"
38
+ )
39
+
40
+ self.thread.start()
41
+
42
+ def stop(self):
43
+
44
+ self.running = False
45
+
46
+ if self.thread:
47
+ self.thread.join()
48
+
49
+ def push(self, symbol, ticks):
50
+
51
+ self.queue.put((symbol, ticks))
52
+
53
+ def _run(self):
54
+
55
+ buffers = {}
56
+
57
+ while self.running:
58
+
59
+ try:
60
+
61
+ symbol, ticks = self.queue.get(timeout=self.FLUSH_INTERVAL)
62
+
63
+ buffers.setdefault(symbol, []).extend(ticks)
64
+
65
+ if len(buffers[symbol]) >= self.MAX_BATCH:
66
+
67
+ self._flush(symbol, buffers)
68
+
69
+ except Empty:
70
+
71
+ for symbol in list(buffers.keys()):
72
+ self._flush(symbol, buffers)
73
+
74
+ def _flush(self, symbol, buffers):
75
+
76
+ ticks = buffers.get(symbol)
77
+
78
+ if not ticks:
79
+ return
80
+
81
+ repo = self.repositories[symbol]
82
+
83
+ repo.conn.execute("BEGIN")
84
+
85
+ try:
86
+
87
+ repo._insert_ticks(symbol, ticks)
88
+
89
+ repo.cache.add_many(ticks)
90
+
91
+ repo.conn.commit()
92
+
93
+ except Exception:
94
+
95
+ repo.conn.rollback()
96
+ raise
97
+
98
+ buffers[symbol] = []
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mtcli"
3
- version = "3.8.0.dev9"
3
+ version = "3.8.0.dev11"
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"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes