mtcli 3.8.0.dev15__tar.gz → 3.8.0.dev17__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.dev15 → mtcli-3.8.0.dev17}/PKG-INFO +2 -1
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/__main__.py +3 -3
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/cli.py +17 -37
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/cli_dev.py +8 -8
- mtcli-3.8.0.dev17/mtcli/commands/backfill.py +70 -0
- mtcli-3.8.0.dev17/mtcli/commands/ticks.py +58 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands_dev/migrate.py +48 -48
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/conf.py +16 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/database.py +137 -137
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/logger.py +178 -136
- mtcli-3.8.0.dev15/mtcli/marketdata/backfill.py → mtcli-3.8.0.dev17/mtcli/marketdata/backfill_engine.py +136 -166
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/marketdata/tick_bus.py +41 -61
- mtcli-3.8.0.dev17/mtcli/marketdata/tick_engine.py +87 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/marketdata/tick_repository.py +12 -9
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/marketdata/tick_writer.py +44 -44
- mtcli-3.8.0.dev17/mtcli/marketdata/trade_tick_filter.py +39 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/001_initial_schema.py +51 -51
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/002_ticks_time_msc.py +53 -53
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/003_optimize_ticks_without_rowid.py +86 -86
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/004_ticks_pk_time_msc.py +78 -78
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/005_tick_price_compression.py +86 -86
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/006_add_index_ticks_symbol_time.py +32 -32
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +91 -91
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/008_fix_ticks_unique_index.py +98 -98
- mtcli-3.8.0.dev17/mtcli/migrations/009_keep_only_trade_ticks.py +84 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/__main__.py +7 -7
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/runner.py +301 -301
- mtcli-3.8.0.dev17/mtcli/services/tick_service.py +56 -0
- mtcli-3.8.0.dev17/mtcli/utils/__init__.py +0 -0
- mtcli-3.8.0.dev17/mtcli/utils/pidfile.py +35 -0
- mtcli-3.8.0.dev17/mtcli/utils/time.py +65 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/pyproject.toml +2 -1
- mtcli-3.8.0.dev15/mtcli/commands/backfill.py +0 -70
- mtcli-3.8.0.dev15/mtcli/commands/ticks.py +0 -44
- mtcli-3.8.0.dev15/mtcli/marketdata/tick_engine.py +0 -151
- mtcli-3.8.0.dev15/mtcli/services/tick_service.py +0 -82
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/LICENSE +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/README.md +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands/bars.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands/conf.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands/doctor.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/commands_dev/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/conecta.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/config_registre.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/data/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/data/base.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/data/csv.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/data/mt5.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/migrations/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/consecutive_bars_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/models/unconsecutive_bar_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/mt5_context.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugin.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugin_loader.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugin_manager.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/cli.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/cli.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/services/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/services/maintenance_service.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/__init__.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/close_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/full_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/high_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/low_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/min_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/open_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/ranges_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/rates_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.8.0.dev15 → mtcli-3.8.0.dev17}/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.
|
|
3
|
+
Version: 3.8.0.dev17
|
|
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
|
|
@@ -19,6 +19,7 @@ Classifier: Operating System :: OS Independent
|
|
|
19
19
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
20
20
|
Requires-Dist: click (>=8.2.1,<9.0.0)
|
|
21
21
|
Requires-Dist: metatrader5 (>=5.0.5260,<6.0.0)
|
|
22
|
+
Requires-Dist: tzdata (>=2025.3,<2026.0)
|
|
22
23
|
Project-URL: Documentation, https://mtcli.readthedocs.io/pt-br/latest
|
|
23
24
|
Project-URL: Homepage, https://github.com:vfranca/mtcli
|
|
24
25
|
Project-URL: Repository, https://github.com/vfranca/mtcli
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .cli import mt
|
|
2
|
-
|
|
3
|
-
mt()
|
|
1
|
+
from .cli import mt
|
|
2
|
+
|
|
3
|
+
mt()
|
|
@@ -1,26 +1,9 @@
|
|
|
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.
|
|
19
3
|
"""
|
|
20
4
|
|
|
21
5
|
import os
|
|
22
6
|
import click
|
|
23
|
-
|
|
24
7
|
from mtcli.plugin_loader import load_plugins
|
|
25
8
|
from mtcli.logger import setup_logger
|
|
26
9
|
from mtcli.services.tick_service import ensure_tick_engine
|
|
@@ -30,43 +13,42 @@ from .commands.doctor import doctor
|
|
|
30
13
|
from .commands.ticks import ticks
|
|
31
14
|
from .commands.backfill import backfill
|
|
32
15
|
|
|
33
|
-
|
|
34
16
|
logger = setup_logger(__name__)
|
|
35
17
|
|
|
36
|
-
|
|
18
|
+
_tick_engines = []
|
|
37
19
|
|
|
38
20
|
|
|
39
21
|
def start_tick_capture():
|
|
40
22
|
"""
|
|
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.
|
|
23
|
+
Inicia captura contínua de ticks em background se MTCLI_SYMBOL estiver definido.
|
|
49
24
|
"""
|
|
50
25
|
|
|
51
|
-
global
|
|
26
|
+
global _tick_engines
|
|
52
27
|
|
|
53
|
-
if
|
|
28
|
+
if _tick_engines:
|
|
54
29
|
return
|
|
55
30
|
|
|
56
|
-
|
|
31
|
+
symbols = os.getenv("MTCLI_SYMBOL")
|
|
57
32
|
|
|
58
|
-
if not
|
|
33
|
+
if not symbols:
|
|
59
34
|
logger.info(
|
|
60
35
|
"Captura automática de ticks desativada (MTCLI_SYMBOL não definido)."
|
|
61
36
|
)
|
|
62
37
|
return
|
|
63
38
|
|
|
64
|
-
|
|
39
|
+
if isinstance(symbols, str):
|
|
40
|
+
symbols = [symbols]
|
|
65
41
|
|
|
66
|
-
|
|
42
|
+
logger.info("Iniciando captura contínua de ticks para %s", symbols)
|
|
67
43
|
|
|
68
|
-
|
|
69
|
-
|
|
44
|
+
try:
|
|
45
|
+
for symbol in symbols:
|
|
46
|
+
engine = ensure_tick_engine(symbol)
|
|
47
|
+
_tick_engines.append(engine)
|
|
48
|
+
# pode rodar em thread se quiser background
|
|
49
|
+
import threading
|
|
50
|
+
t = threading.Thread(target=engine.start, daemon=True)
|
|
51
|
+
t.start()
|
|
70
52
|
|
|
71
53
|
logger.info("Captura de ticks iniciada em background.")
|
|
72
54
|
|
|
@@ -95,7 +77,6 @@ mt.add_command(ticks)
|
|
|
95
77
|
mt.add_command(backfill, name="fill")
|
|
96
78
|
|
|
97
79
|
loaded_plugins = load_plugins(mt)
|
|
98
|
-
|
|
99
80
|
logger.info("Plugins carregados: %s", loaded_plugins)
|
|
100
81
|
|
|
101
82
|
|
|
@@ -110,7 +91,6 @@ def list_plugins():
|
|
|
110
91
|
return
|
|
111
92
|
|
|
112
93
|
click.echo("Plugins carregados:\n")
|
|
113
|
-
|
|
114
94
|
for name in loaded_plugins:
|
|
115
95
|
click.echo(f" {name}")
|
|
116
96
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from .commands_dev.migrate import migrate
|
|
3
|
-
|
|
4
|
-
@click.group()
|
|
5
|
-
def cli():
|
|
6
|
-
pass
|
|
7
|
-
|
|
8
|
-
cli.add_command(migrate)
|
|
1
|
+
import click
|
|
2
|
+
from .commands_dev.migrate import migrate
|
|
3
|
+
|
|
4
|
+
@click.group()
|
|
5
|
+
def cli():
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
cli.add_command(migrate)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comando CLI: backfill
|
|
3
|
+
|
|
4
|
+
Carrega histórico de ticks do MetaTrader5
|
|
5
|
+
para o banco SQLite do mtcli utilizando o BackfillEngine
|
|
6
|
+
com filtro de trade ticks.
|
|
7
|
+
|
|
8
|
+
Fluxo:
|
|
9
|
+
|
|
10
|
+
BackfillEngine
|
|
11
|
+
->
|
|
12
|
+
raw_tick_bus
|
|
13
|
+
->
|
|
14
|
+
TradeTickFilter
|
|
15
|
+
->
|
|
16
|
+
trade_tick_bus
|
|
17
|
+
->
|
|
18
|
+
TickWriter / plugins
|
|
19
|
+
->
|
|
20
|
+
TickRepository
|
|
21
|
+
->
|
|
22
|
+
SQLite
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import click
|
|
26
|
+
|
|
27
|
+
from mtcli.logger import setup_logger
|
|
28
|
+
from mtcli.marketdata.tick_bus import TickBus
|
|
29
|
+
from mtcli.marketdata.tick_repository import TickRepository
|
|
30
|
+
from mtcli.marketdata.tick_writer import TickWriter
|
|
31
|
+
from mtcli.marketdata.trade_tick_filter import TradeTickFilter
|
|
32
|
+
from mtcli.marketdata.backfill_engine import BackfillEngine
|
|
33
|
+
|
|
34
|
+
logger = setup_logger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.command()
|
|
38
|
+
@click.argument("symbol")
|
|
39
|
+
@click.option(
|
|
40
|
+
"--days",
|
|
41
|
+
default=5,
|
|
42
|
+
show_default=True,
|
|
43
|
+
)
|
|
44
|
+
def backfill(symbol: str, days: int):
|
|
45
|
+
|
|
46
|
+
raw_tick_bus = TickBus()
|
|
47
|
+
trade_tick_bus = TickBus()
|
|
48
|
+
|
|
49
|
+
repo = TickRepository()
|
|
50
|
+
|
|
51
|
+
raw_tick_bus.subscribe(
|
|
52
|
+
TradeTickFilter(trade_tick_bus)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
writer = TickWriter(symbol, repo)
|
|
56
|
+
|
|
57
|
+
trade_tick_bus.subscribe(writer)
|
|
58
|
+
|
|
59
|
+
engine = BackfillEngine(
|
|
60
|
+
symbol,
|
|
61
|
+
raw_tick_bus,
|
|
62
|
+
repo,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
engine.run(days)
|
|
66
|
+
|
|
67
|
+
logger.info(
|
|
68
|
+
"Backfill concluído (%s) — trade ticks gravados",
|
|
69
|
+
symbol,
|
|
70
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comando CLI para captura contínua de ticks.
|
|
3
|
+
|
|
4
|
+
Exemplo:
|
|
5
|
+
mt ticks WIN$N
|
|
6
|
+
mt ticks WIN$N WDO$N PETR4
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from mtcli.services.tick_service import ensure_tick_engine
|
|
13
|
+
from mtcli.logger import setup_logger
|
|
14
|
+
|
|
15
|
+
logger = setup_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.argument("symbols", nargs=-1)
|
|
20
|
+
def ticks(symbols):
|
|
21
|
+
|
|
22
|
+
if not symbols:
|
|
23
|
+
|
|
24
|
+
click.echo("Informe ao menos um símbolo.")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
engines = []
|
|
28
|
+
|
|
29
|
+
for symbol in symbols:
|
|
30
|
+
|
|
31
|
+
engine = ensure_tick_engine(symbol)
|
|
32
|
+
|
|
33
|
+
t = threading.Thread(
|
|
34
|
+
target=engine.start,
|
|
35
|
+
daemon=True
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
t.start()
|
|
39
|
+
|
|
40
|
+
engines.append(engine)
|
|
41
|
+
|
|
42
|
+
click.echo(
|
|
43
|
+
f"Captura de ticks iniciada para {symbol}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
|
|
48
|
+
while True:
|
|
49
|
+
|
|
50
|
+
for engine in engines:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
except KeyboardInterrupt:
|
|
54
|
+
|
|
55
|
+
click.echo("\nEncerrando captura...")
|
|
56
|
+
|
|
57
|
+
for engine in engines:
|
|
58
|
+
engine.stop()
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Comando CLI para execução das migrations do banco de dados.
|
|
3
|
-
|
|
4
|
-
Este comando aplica todas as migrations pendentes no banco
|
|
5
|
-
utilizado pelo mtcli.
|
|
6
|
-
|
|
7
|
-
Fluxo de execução:
|
|
8
|
-
|
|
9
|
-
1. Abre conexão com o banco SQLite
|
|
10
|
-
2. Executa o migration runner
|
|
11
|
-
3. Aplica migrations ainda não executadas
|
|
12
|
-
|
|
13
|
-
Uso:
|
|
14
|
-
|
|
15
|
-
mtcli migrate
|
|
16
|
-
|
|
17
|
-
Este comando é implementado utilizando o framework
|
|
18
|
-
:contentReference[oaicite:1]{index=1} para construção de aplicações CLI.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
import click
|
|
22
|
-
|
|
23
|
-
from mtcli.database import get_connection
|
|
24
|
-
from mtcli.migrations.runner import run_migrations
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@click.command()
|
|
28
|
-
def migrate():
|
|
29
|
-
"""
|
|
30
|
-
Executa as migrations pendentes do banco de dados.
|
|
31
|
-
|
|
32
|
-
O comando conecta ao banco configurado no mtcli
|
|
33
|
-
e executa o migration runner responsável por:
|
|
34
|
-
|
|
35
|
-
- detectar migrations disponíveis
|
|
36
|
-
- identificar a versão atual do schema
|
|
37
|
-
- aplicar migrations pendentes
|
|
38
|
-
- registrar migrations aplicadas
|
|
39
|
-
|
|
40
|
-
Este comando é normalmente executado:
|
|
41
|
-
|
|
42
|
-
- na primeira inicialização do sistema
|
|
43
|
-
- após atualização de versão do mtcli
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
conn = get_connection()
|
|
47
|
-
|
|
48
|
-
run_migrations(conn)
|
|
1
|
+
"""
|
|
2
|
+
Comando CLI para execução das migrations do banco de dados.
|
|
3
|
+
|
|
4
|
+
Este comando aplica todas as migrations pendentes no banco
|
|
5
|
+
utilizado pelo mtcli.
|
|
6
|
+
|
|
7
|
+
Fluxo de execução:
|
|
8
|
+
|
|
9
|
+
1. Abre conexão com o banco SQLite
|
|
10
|
+
2. Executa o migration runner
|
|
11
|
+
3. Aplica migrations ainda não executadas
|
|
12
|
+
|
|
13
|
+
Uso:
|
|
14
|
+
|
|
15
|
+
mtcli migrate
|
|
16
|
+
|
|
17
|
+
Este comando é implementado utilizando o framework
|
|
18
|
+
:contentReference[oaicite:1]{index=1} para construção de aplicações CLI.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
from mtcli.database import get_connection
|
|
24
|
+
from mtcli.migrations.runner import run_migrations
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
def migrate():
|
|
29
|
+
"""
|
|
30
|
+
Executa as migrations pendentes do banco de dados.
|
|
31
|
+
|
|
32
|
+
O comando conecta ao banco configurado no mtcli
|
|
33
|
+
e executa o migration runner responsável por:
|
|
34
|
+
|
|
35
|
+
- detectar migrations disponíveis
|
|
36
|
+
- identificar a versão atual do schema
|
|
37
|
+
- aplicar migrations pendentes
|
|
38
|
+
- registrar migrations aplicadas
|
|
39
|
+
|
|
40
|
+
Este comando é normalmente executado:
|
|
41
|
+
|
|
42
|
+
- na primeira inicialização do sistema
|
|
43
|
+
- após atualização de versão do mtcli
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
conn = get_connection()
|
|
47
|
+
|
|
48
|
+
run_migrations(conn)
|
|
@@ -313,3 +313,19 @@ _INITIAL_CSV_PATH = conf.get_csv_path()
|
|
|
313
313
|
# ---------------------------------------------------------
|
|
314
314
|
|
|
315
315
|
DB_NAME = conf.get("db_name", default="marketdata.db")
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------
|
|
318
|
+
# gestão de múltiplos processos
|
|
319
|
+
# ---------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
RUN_DIR = os.path.join(
|
|
322
|
+
os.getenv("APPDATA", os.path.expanduser("~")),
|
|
323
|
+
"mtcli",
|
|
324
|
+
"run"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
os.makedirs(RUN_DIR, exist_ok=True)
|
|
328
|
+
|
|
329
|
+
PID_FILE = os.path.join(RUN_DIR, "risco.pid")
|
|
330
|
+
STOP_FILE = os.path.join(RUN_DIR, "risco.stop")
|
|
331
|
+
HEARTBEAT_FILE = os.path.join(RUN_DIR, "risco.heartbeat")
|
|
@@ -1,137 +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
|
|
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
|