mtcli 3.8.0.dev13__tar.gz → 3.8.0.dev15__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 (98) hide show
  1. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/PKG-INFO +1 -1
  2. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/backfill.py +70 -68
  3. mtcli-3.8.0.dev15/mtcli/marketdata/backfill.py +166 -0
  4. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/marketdata/tick_bus.py +61 -45
  5. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/marketdata/tick_repository.py +330 -330
  6. mtcli-3.8.0.dev15/mtcli/marketdata/tick_writer.py +44 -0
  7. mtcli-3.8.0.dev15/mtcli/migrations/008_fix_ticks_unique_index.py +98 -0
  8. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/runner.py +65 -9
  9. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/pyproject.toml +1 -1
  10. mtcli-3.8.0.dev13/mtcli/marketdata/backfill_engine.py +0 -201
  11. mtcli-3.8.0.dev13/mtcli/marketdata/tick_writer.py +0 -38
  12. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/LICENSE +0 -0
  13. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/README.md +0 -0
  14. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/__init__.py +0 -0
  15. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/__main__.py +0 -0
  16. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/cli.py +0 -0
  17. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/cli_dev.py +0 -0
  18. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/__init__.py +0 -0
  19. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/bars.py +0 -0
  20. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/conf.py +0 -0
  21. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/doctor.py +0 -0
  22. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands/ticks.py +0 -0
  23. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands_dev/__init__.py +0 -0
  24. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/commands_dev/migrate.py +0 -0
  25. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/conecta.py +0 -0
  26. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/conf.py +0 -0
  27. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/config_registre.py +0 -0
  28. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/data/__init__.py +0 -0
  29. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/data/base.py +0 -0
  30. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/data/csv.py +0 -0
  31. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/data/mt5.py +0 -0
  32. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/database.py +0 -0
  33. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/domain/__init__.py +0 -0
  34. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/domain/timeframe.py +0 -0
  35. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/logger.py +0 -0
  36. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/marketdata/__init__.py +0 -0
  37. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/marketdata/tick_cache.py +0 -0
  38. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/marketdata/tick_engine.py +0 -0
  39. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/001_initial_schema.py +0 -0
  40. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/002_ticks_time_msc.py +0 -0
  41. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
  42. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/004_ticks_pk_time_msc.py +0 -0
  43. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/005_tick_price_compression.py +0 -0
  44. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/006_add_index_ticks_symbol_time.py +0 -0
  45. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +0 -0
  46. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/__init__.py +0 -0
  47. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/migrations/__main__.py +0 -0
  48. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/__init__.py +0 -0
  49. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/bar_model.py +0 -0
  50. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/bars_model.py +0 -0
  51. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/chart_model.py +0 -0
  52. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/conf_model.py +0 -0
  53. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/consecutive_bars_model.py +0 -0
  54. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/rates_model.py +0 -0
  55. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/signals_model.py +0 -0
  56. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/models/unconsecutive_bar_model.py +0 -0
  57. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/mt5_context.py +0 -0
  58. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugin.py +0 -0
  59. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugin_loader.py +0 -0
  60. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugin_manager.py +0 -0
  61. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/__init__.py +0 -0
  62. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/exemplo.py-dist +0 -0
  63. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/__init__.py +0 -0
  64. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/cli.py +0 -0
  65. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/conf.py +0 -0
  66. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  67. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  68. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  69. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  70. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  71. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/__init__.py +0 -0
  72. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/cli.py +0 -0
  73. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/conf.py +0 -0
  74. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  75. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  76. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  77. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  78. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/__init__.py +0 -0
  79. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/cli.py +0 -0
  80. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/conf.py +0 -0
  81. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  82. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  83. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  84. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  85. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/services/__init__.py +0 -0
  86. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/services/maintenance_service.py +0 -0
  87. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/services/tick_service.py +0 -0
  88. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/__init__.py +0 -0
  89. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/close_view.py +0 -0
  90. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/full_view.py +0 -0
  91. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/high_view.py +0 -0
  92. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/low_view.py +0 -0
  93. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/min_view.py +0 -0
  94. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/open_view.py +0 -0
  95. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/ranges_view.py +0 -0
  96. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/rates_view.py +0 -0
  97. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/mtcli/views/vars_view.py +0 -0
  98. {mtcli-3.8.0.dev13 → mtcli-3.8.0.dev15}/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.dev13
3
+ Version: 3.8.0.dev15
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,68 +1,70 @@
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 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
+ TickBus
12
+
13
+ TickWriter
14
+
15
+ TickRepository
16
+
17
+ SQLite
18
+ """
19
+
20
+ import click
21
+
22
+ from mtcli.marketdata.backfill import BackfillEngine
23
+ from mtcli.marketdata.tick_bus import TickBus
24
+ from mtcli.marketdata.tick_repository import TickRepository
25
+ from mtcli.marketdata.tick_writer import TickWriter
26
+
27
+
28
+ @click.command()
29
+ @click.argument("symbol")
30
+ @click.option(
31
+ "--days",
32
+ default=5,
33
+ show_default=True,
34
+ help="Número de dias de histórico a carregar caso não exista histórico local.",
35
+ )
36
+ def backfill(symbol: str, days: int):
37
+ """
38
+ Executa backfill histórico de ticks.
39
+
40
+ Este comando baixa ticks históricos diretamente do MetaTrader5
41
+ e os grava no banco local SQLite do mtcli.
42
+
43
+ O processo é incremental:
44
+
45
+ - se já existirem ticks no banco, o backfill continua do último tick
46
+ - caso contrário, carrega o número de dias definido em --days
47
+
48
+ Examples
49
+ --------
50
+
51
+ Carregar 5 dias:
52
+
53
+ mt fill WINJ26
54
+
55
+ Carregar 30 dias:
56
+
57
+ mt fill WINJ26 --days 30
58
+ """
59
+
60
+ bus = TickBus()
61
+
62
+ repo = TickRepository()
63
+
64
+ writer = TickWriter(symbol, repo)
65
+
66
+ bus.subscribe(writer)
67
+
68
+ engine = BackfillEngine(symbol, bus, repo)
69
+
70
+ engine.run(days)
@@ -0,0 +1,166 @@
1
+ """
2
+ BackfillEngine
3
+
4
+ Carrega ticks históricos do MetaTrader5 de forma determinística
5
+ utilizando paginação por janelas de tempo.
6
+
7
+ Vantagens sobre copy_ticks_from():
8
+
9
+ • evita duplicação
10
+ • evita buracos de ticks
11
+ • não entra em loop
12
+ • desempenho previsível
13
+ • seguro para reexecução (idempotente)
14
+
15
+ Arquitetura:
16
+
17
+ MT5
18
+
19
+ BackfillEngine
20
+
21
+ TickBus
22
+
23
+ Subscribers (TickWriter, plugins, etc)
24
+ """
25
+
26
+ import datetime
27
+ import MetaTrader5 as mt5
28
+
29
+ from mtcli.logger import setup_logger
30
+ from mtcli.mt5_context import mt5_conexao
31
+
32
+ logger = setup_logger(__name__)
33
+
34
+
35
+ class BackfillEngine:
36
+ """
37
+ Engine responsável pelo carregamento histórico de ticks.
38
+ """
39
+
40
+ WINDOW_MINUTES = 10
41
+
42
+ def __init__(self, symbol, tick_bus, repository):
43
+
44
+ self.symbol = symbol
45
+ self.tick_bus = tick_bus
46
+ self.repository = repository
47
+
48
+ self.last_time_msc = None
49
+
50
+ # ---------------------------------------------------------
51
+
52
+ def _get_last_stored(self):
53
+
54
+ last = self.repository._get_last_tick_msc(self.symbol)
55
+
56
+ if last:
57
+ logger.info(
58
+ "Backfill retomando do último tick armazenado: %s",
59
+ last,
60
+ )
61
+
62
+ return last
63
+
64
+ # ---------------------------------------------------------
65
+
66
+ def run(self, days=5):
67
+
68
+ logger.info(
69
+ "Backfill iniciado (%s) — até %s dias de histórico",
70
+ self.symbol,
71
+ days,
72
+ )
73
+
74
+ with mt5_conexao():
75
+
76
+ mt5.symbol_select(self.symbol, True)
77
+
78
+ now = datetime.datetime.now()
79
+
80
+ last = self._get_last_stored()
81
+
82
+ if last:
83
+
84
+ self.last_time_msc = last
85
+
86
+ start = datetime.datetime.fromtimestamp(
87
+ (last + 1) / 1000
88
+ )
89
+
90
+ else:
91
+
92
+ start = now - datetime.timedelta(days=days)
93
+
94
+ end = now
95
+
96
+ total_loaded = 0
97
+
98
+ while start < end:
99
+
100
+ chunk_end = start + datetime.timedelta(
101
+ minutes=self.WINDOW_MINUTES
102
+ )
103
+
104
+ if chunk_end > end:
105
+ chunk_end = end
106
+
107
+ ticks = mt5.copy_ticks_range(
108
+ self.symbol,
109
+ start,
110
+ chunk_end,
111
+ mt5.COPY_TICKS_ALL,
112
+ )
113
+
114
+ if ticks is None or len(ticks) == 0:
115
+
116
+ start = chunk_end
117
+ continue
118
+
119
+ # ---------------------------------------------
120
+ # filtro extra de segurança
121
+ # ---------------------------------------------
122
+
123
+ if self.last_time_msc:
124
+
125
+ mask = ticks["time_msc"] > self.last_time_msc
126
+ ticks = ticks[mask]
127
+
128
+ if len(ticks) == 0:
129
+
130
+ start = chunk_end
131
+ continue
132
+
133
+ # ---------------------------------------------
134
+ # publica ticks
135
+ # ---------------------------------------------
136
+
137
+ self.tick_bus.publish_many(ticks)
138
+
139
+ last_msc = int(ticks["time_msc"][-1])
140
+
141
+ if last_msc == self.last_time_msc:
142
+
143
+ logger.warning(
144
+ "Proteção de loop ativada — encerrando backfill"
145
+ )
146
+ break
147
+
148
+ self.last_time_msc = last_msc
149
+
150
+ total_loaded += len(ticks)
151
+
152
+ if total_loaded % 1_000_000 < len(ticks):
153
+
154
+ logger.info(
155
+ "Backfill progresso (%s): %s ticks",
156
+ self.symbol,
157
+ f"{total_loaded:,}",
158
+ )
159
+
160
+ start = chunk_end
161
+
162
+ logger.info(
163
+ "Backfill concluído (%s) — %s ticks processados",
164
+ self.symbol,
165
+ f"{total_loaded:,}",
166
+ )
@@ -1,45 +1,61 @@
1
- """
2
- TickBus
3
-
4
- Event Bus simples para distribuição de ticks.
5
- """
6
-
7
- import logging
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- class TickBus:
13
- """
14
- Implementa um Event Bus simples para ticks.
15
- """
16
-
17
- def __init__(self):
18
- self.subscribers = []
19
-
20
- # ---------------------------------------------------------
21
-
22
- def subscribe(self, handler):
23
- """
24
- Registra um subscriber para receber ticks.
25
- """
26
-
27
- self.subscribers.append(handler)
28
-
29
- name = getattr(handler, "__qualname__", handler.__class__.__name__)
30
-
31
- logger.debug("Subscriber registrado: %s", name)
32
-
33
- # ---------------------------------------------------------
34
-
35
- def publish(self, tick):
36
- """
37
- Publica um tick para todos os subscribers.
38
- """
39
-
40
- for handler in self.subscribers:
41
- try:
42
- handler(tick)
43
-
44
- except Exception:
45
- logger.exception("Erro em subscriber")
1
+ """
2
+ TickBus
3
+
4
+ Event Bus simples para distribuição de ticks.
5
+ """
6
+
7
+ from mtcli.logger import setup_logger
8
+
9
+ logger = setup_logger(__name__)
10
+
11
+
12
+ class TickBus:
13
+ """
14
+ Implementa um Event Bus simples para ticks.
15
+ """
16
+
17
+ def __init__(self):
18
+ self.subscribers = []
19
+
20
+ # ---------------------------------------------------------
21
+
22
+ def subscribe(self, handler):
23
+ """
24
+ Registra um subscriber para receber ticks.
25
+ """
26
+
27
+ self.subscribers.append(handler)
28
+
29
+ name = getattr(handler, "__qualname__", handler.__class__.__name__)
30
+
31
+ logger.debug("Subscriber registrado: %s", name)
32
+
33
+ # ---------------------------------------------------------
34
+
35
+ def publish(self, tick):
36
+ """
37
+ Publica um único tick para todos os subscribers.
38
+ """
39
+
40
+ for handler in self.subscribers:
41
+ try:
42
+ handler(tick)
43
+
44
+ except Exception:
45
+ logger.exception("Erro em subscriber")
46
+
47
+ # ---------------------------------------------------------
48
+
49
+ def publish_many(self, ticks):
50
+ """
51
+ Publica um lote de ticks para todos os subscribers.
52
+
53
+ Usado principalmente por mecanismos de backfill.
54
+ """
55
+
56
+ for handler in self.subscribers:
57
+ try:
58
+ handler(ticks)
59
+
60
+ except Exception:
61
+ logger.exception("Erro em subscriber")