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.
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/PKG-INFO +1 -1
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/cli.py +119 -119
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/backfill.py +68 -68
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/ticks.py +44 -44
- mtcli-3.8.0.dev13/mtcli/database.py +137 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/backfill_engine.py +201 -201
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_bus.py +45 -45
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_cache.py +160 -160
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_engine.py +151 -151
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_repository.py +330 -330
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/tick_writer.py +38 -38
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/maintenance_service.py +170 -170
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/tick_service.py +82 -82
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/pyproject.toml +1 -1
- mtcli-3.8.0.dev12/mtcli/database.py +0 -77
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/LICENSE +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/README.md +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/__main__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/cli_dev.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/bars.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/conf.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands/doctor.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands_dev/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/commands_dev/migrate.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/conecta.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/conf.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/config_registre.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/base.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/csv.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/data/mt5.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/logger.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/001_initial_schema.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/002_ticks_time_msc.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/004_ticks_pk_time_msc.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/005_tick_price_compression.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/006_add_index_ticks_symbol_time.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/__main__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/migrations/runner.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/consecutive_bars_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/models/unconsecutive_bar_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/mt5_context.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin_loader.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugin_manager.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/cli.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/cli.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/services/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/__init__.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/close_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/full_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/high_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/low_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/min_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/open_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/ranges_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/rates_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.8.0.dev12 → mtcli-3.8.0.dev13}/mtcli/views/volumes_view.py +0 -0
|
@@ -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
|