mtcli 3.8.0.dev12__tar.gz → 3.8.0.dev13__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 (96) hide show
  1. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/PKG-INFO +1 -1
  2. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/cli.py +119 -119
  3. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/backfill.py +68 -68
  4. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/ticks.py +44 -44
  5. mtcli-3.8.0.dev13/mtcli/database.py +137 -0
  6. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/backfill_engine.py +201 -201
  7. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_bus.py +45 -45
  8. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_cache.py +160 -160
  9. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_engine.py +151 -151
  10. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_repository.py +330 -330
  11. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_writer.py +38 -38
  12. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/maintenance_service.py +170 -170
  13. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/tick_service.py +82 -82
  14. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/pyproject.toml +1 -1
  15. mtcli-3.8.0.dev12/mtcli/database.py +0 -77
  16. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/LICENSE +0 -0
  17. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/README.md +0 -0
  18. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/__init__.py +0 -0
  19. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/__main__.py +0 -0
  20. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/cli_dev.py +0 -0
  21. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/__init__.py +0 -0
  22. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/bars.py +0 -0
  23. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/conf.py +0 -0
  24. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/doctor.py +0 -0
  25. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands_dev/__init__.py +0 -0
  26. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands_dev/migrate.py +0 -0
  27. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/conecta.py +0 -0
  28. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/conf.py +0 -0
  29. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/config_registre.py +0 -0
  30. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/__init__.py +0 -0
  31. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/base.py +0 -0
  32. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/csv.py +0 -0
  33. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/mt5.py +0 -0
  34. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/domain/__init__.py +0 -0
  35. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/domain/timeframe.py +0 -0
  36. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/logger.py +0 -0
  37. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/__init__.py +0 -0
  38. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/001_initial_schema.py +0 -0
  39. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/002_ticks_time_msc.py +0 -0
  40. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
  41. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/004_ticks_pk_time_msc.py +0 -0
  42. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/005_tick_price_compression.py +0 -0
  43. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/006_add_index_ticks_symbol_time.py +0 -0
  44. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +0 -0
  45. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/__init__.py +0 -0
  46. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/__main__.py +0 -0
  47. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/runner.py +0 -0
  48. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/__init__.py +0 -0
  49. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/bar_model.py +0 -0
  50. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/bars_model.py +0 -0
  51. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/chart_model.py +0 -0
  52. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/conf_model.py +0 -0
  53. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/consecutive_bars_model.py +0 -0
  54. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/rates_model.py +0 -0
  55. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/signals_model.py +0 -0
  56. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/unconsecutive_bar_model.py +0 -0
  57. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/mt5_context.py +0 -0
  58. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin.py +0 -0
  59. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin_loader.py +0 -0
  60. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin_manager.py +0 -0
  61. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/__init__.py +0 -0
  62. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/exemplo.py-dist +0 -0
  63. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/__init__.py +0 -0
  64. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/cli.py +0 -0
  65. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/conf.py +0 -0
  66. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/models/__init__.py +0 -0
  67. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
  68. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
  69. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
  70. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
  71. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/__init__.py +0 -0
  72. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/cli.py +0 -0
  73. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/conf.py +0 -0
  74. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/models/__init__.py +0 -0
  75. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
  76. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
  77. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
  78. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/__init__.py +0 -0
  79. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/cli.py +0 -0
  80. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/conf.py +0 -0
  81. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
  82. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
  83. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
  84. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
  85. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/__init__.py +0 -0
  86. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/__init__.py +0 -0
  87. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/close_view.py +0 -0
  88. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/full_view.py +0 -0
  89. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/high_view.py +0 -0
  90. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/low_view.py +0 -0
  91. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/min_view.py +0 -0
  92. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/open_view.py +0 -0
  93. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/ranges_view.py +0 -0
  94. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/rates_view.py +0 -0
  95. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/vars_view.py +0 -0
  96. {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/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.dev12
3
+ Version: 3.8.0.dev13
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,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)
@@ -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()
@@ -0,0 +1,137 @@
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
8
+ - executar migrations automaticamente
9
+ - backup automático
10
+ """
11
+
12
+ import sqlite3
13
+ from pathlib import Path
14
+ from datetime import datetime
15
+
16
+ from .conf import DB_NAME
17
+ from .migrations.runner import run_migrations
18
+ from .logger import setup_logger
19
+
20
+
21
+ # ==========================================================
22
+ # LOGGER
23
+ # ==========================================================
24
+
25
+ log = setup_logger(__name__)
26
+
27
+
28
+ # ==========================================================
29
+ # PATHS
30
+ # ==========================================================
31
+
32
+ DB_PATH = Path.home() / ".mtcli" / DB_NAME
33
+ BACKUP_DIR = Path.home() / ".mtcli" / "backups"
34
+
35
+ _connection = None
36
+
37
+
38
+ # ==========================================================
39
+ # CONNECTION
40
+ # ==========================================================
41
+
42
+ def get_connection():
43
+ """
44
+ Retorna conexão singleton SQLite.
45
+ """
46
+
47
+ global _connection
48
+
49
+ if _connection:
50
+ log.debug("Reutilizando conexão SQLite existente.")
51
+ return _connection
52
+
53
+ log.debug("Inicializando conexão SQLite.")
54
+
55
+ DB_PATH.parent.mkdir(parents=True, exist_ok=True)
56
+ BACKUP_DIR.mkdir(parents=True, exist_ok=True)
57
+
58
+ log.debug("Diretórios garantidos: db=%s backups=%s", DB_PATH.parent, BACKUP_DIR)
59
+
60
+ conn = sqlite3.connect(DB_PATH, check_same_thread=False)
61
+
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
+
70
+ conn.execute("PRAGMA journal_mode=WAL")
71
+ conn.execute("PRAGMA synchronous=NORMAL")
72
+ conn.execute("PRAGMA temp_store=MEMORY")
73
+ conn.execute("PRAGMA mmap_size=30000000000")
74
+ conn.execute("PRAGMA cache_size=-200000")
75
+ conn.execute("PRAGMA journal_size_limit=67108864")
76
+ conn.execute("PRAGMA wal_autocheckpoint=5000")
77
+ conn.execute("PRAGMA foreign_keys=ON")
78
+
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
93
+
94
+ _connection = conn
95
+
96
+ log.debug("Conexão SQLite inicializada e armazenada como singleton.")
97
+
98
+ return conn
99
+
100
+
101
+ # ==========================================================
102
+ # BACKUP
103
+ # ==========================================================
104
+
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
+ """
111
+
112
+ now = datetime.now().strftime("%Y%m%d")
113
+
114
+ backup_path = BACKUP_DIR / f"marketdata_{now}.db"
115
+
116
+ log.debug("Verificando necessidade de backup diário: %s", backup_path)
117
+
118
+ if backup_path.exists():
119
+ log.debug("Backup diário já existe. Ignorando backup.")
120
+ return
121
+
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()
132
+
133
+ log.info("Backup concluído com sucesso.")
134
+
135
+ except Exception:
136
+ log.exception("Falha durante backup do banco.")
137
+ raise