mtcli 3.8.0.dev4__tar.gz → 3.8.0.dev5__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.dev4 → mtcli-3.8.0.dev5}/PKG-INFO +1 -1
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/cli.py +41 -16
- mtcli-3.8.0.dev5/mtcli/commands/ticks.py +47 -0
- mtcli-3.8.0.dev5/mtcli/marketdata/tick_engine.py +134 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/marketdata/tick_repository.py +39 -38
- mtcli-3.8.0.dev5/mtcli/migrations/004_ticks_pk_time_msc.py +56 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/pyproject.toml +1 -1
- mtcli-3.8.0.dev4/mtcli/marketdata/tick_engine.py +0 -93
- mtcli-3.8.0.dev4/mtcli/marketdata/tick_streamer.py +0 -62
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/LICENSE +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/README.md +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/commands/bars.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/commands/conf.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/commands/doctor.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/commands/migrate.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/conecta.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/conf.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/config_registre.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/data/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/data/base.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/data/csv.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/data/mt5.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/database.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/logger.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/migrations/001_initial_schema.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/migrations/002_ticks_time_msc.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/migrations/003_optimize_ticks_without_rowid.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/migrations/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/migrations/runner.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/consecutive_bars_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/models/unconsecutive_bar_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/mt5_context.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugin.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugin_loader.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugin_manager.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/cli.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/cli.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/__init__.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/close_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/full_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/high_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/low_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/min_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/open_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/ranges_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/rates_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/views/volumes_view.py +0 -0
|
@@ -1,56 +1,74 @@
|
|
|
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.
|
|
3
19
|
"""
|
|
4
20
|
|
|
5
21
|
import os
|
|
6
|
-
import threading
|
|
7
22
|
import click
|
|
8
23
|
|
|
9
24
|
from mtcli.plugin_loader import load_plugins
|
|
10
25
|
from mtcli.logger import setup_logger
|
|
11
|
-
from mtcli.marketdata.
|
|
26
|
+
from mtcli.marketdata.tick_engine import TickEngine
|
|
12
27
|
|
|
13
28
|
from .commands.bars import bars
|
|
14
29
|
from .commands.doctor import doctor
|
|
15
30
|
from .commands.migrate import migrate
|
|
31
|
+
from .commands.ticks import ticks
|
|
16
32
|
|
|
17
33
|
|
|
18
34
|
logger = setup_logger(__name__)
|
|
19
35
|
|
|
20
|
-
|
|
36
|
+
_tick_engine = None
|
|
21
37
|
|
|
22
38
|
|
|
23
39
|
def start_tick_capture():
|
|
40
|
+
"""
|
|
41
|
+
Inicia captura contínua de ticks em background.
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
Se a variável de ambiente MTCLI_SYMBOL estiver definida,
|
|
44
|
+
o mtcli iniciará automaticamente um TickEngine para
|
|
45
|
+
coletar ticks continuamente.
|
|
26
46
|
|
|
27
|
-
|
|
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:
|
|
28
54
|
return
|
|
29
55
|
|
|
30
56
|
symbol = os.getenv("MTCLI_SYMBOL")
|
|
31
57
|
|
|
32
58
|
if not symbol:
|
|
33
|
-
logger.info("Captura de ticks desativada (MTCLI_SYMBOL não definido).")
|
|
59
|
+
logger.info("Captura automática de ticks desativada (MTCLI_SYMBOL não definido).")
|
|
34
60
|
return
|
|
35
61
|
|
|
36
62
|
logger.info("Iniciando captura contínua de ticks para %s", symbol)
|
|
37
63
|
|
|
38
64
|
try:
|
|
39
65
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
thread = threading.Thread(
|
|
43
|
-
target=_tick_streamer.start,
|
|
44
|
-
daemon=True,
|
|
45
|
-
name="mtcli-tick-streamer",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
thread.start()
|
|
66
|
+
_tick_engine = TickEngine([symbol])
|
|
67
|
+
_tick_engine.start()
|
|
49
68
|
|
|
50
69
|
logger.info("Captura de ticks iniciada em background.")
|
|
51
70
|
|
|
52
71
|
except Exception:
|
|
53
|
-
|
|
54
72
|
logger.exception("Falha ao iniciar captura de ticks")
|
|
55
73
|
|
|
56
74
|
|
|
@@ -58,6 +76,9 @@ def start_tick_capture():
|
|
|
58
76
|
@click.version_option(package_name="mtcli")
|
|
59
77
|
@click.pass_context
|
|
60
78
|
def mt(ctx):
|
|
79
|
+
"""
|
|
80
|
+
CLI principal do mtcli.
|
|
81
|
+
"""
|
|
61
82
|
|
|
62
83
|
start_tick_capture()
|
|
63
84
|
|
|
@@ -69,6 +90,7 @@ mt.add_command(doctor, name="doctor")
|
|
|
69
90
|
mt.add_command(bars, name="bars")
|
|
70
91
|
mt.add_command(doctor, name="dr")
|
|
71
92
|
mt.add_command(migrate)
|
|
93
|
+
mt.add_command(ticks)
|
|
72
94
|
|
|
73
95
|
loaded_plugins = load_plugins(mt)
|
|
74
96
|
|
|
@@ -77,6 +99,9 @@ logger.info("Plugins carregados: %s", loaded_plugins)
|
|
|
77
99
|
|
|
78
100
|
@mt.command(name="plugins")
|
|
79
101
|
def list_plugins():
|
|
102
|
+
"""
|
|
103
|
+
Lista os plugins carregados no mtcli.
|
|
104
|
+
"""
|
|
80
105
|
|
|
81
106
|
if not loaded_plugins:
|
|
82
107
|
click.echo("Nenhum plugin carregado.")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comando CLI para captura contínua de ticks.
|
|
3
|
+
|
|
4
|
+
Permite iniciar um TickEngine manualmente
|
|
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
|
+
A captura continua até o usuário interromper
|
|
13
|
+
com Ctrl+C.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import time
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from mtcli.marketdata.tick_engine import TickEngine
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.argument("symbols", nargs=-1)
|
|
24
|
+
def ticks(symbols):
|
|
25
|
+
"""
|
|
26
|
+
Inicia captura contínua de ticks para os símbolos informados.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if not symbols:
|
|
30
|
+
click.echo("Informe ao menos um símbolo.")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
engine = TickEngine(symbols)
|
|
34
|
+
|
|
35
|
+
click.echo("Iniciando captura de ticks...")
|
|
36
|
+
|
|
37
|
+
engine.start()
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
|
|
41
|
+
while True:
|
|
42
|
+
time.sleep(1)
|
|
43
|
+
|
|
44
|
+
except KeyboardInterrupt:
|
|
45
|
+
|
|
46
|
+
click.echo("\nEncerrando captura de ticks...")
|
|
47
|
+
engine.stop()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TickEngine - motor de captura contínua de ticks.
|
|
3
|
+
|
|
4
|
+
Responsável por:
|
|
5
|
+
|
|
6
|
+
- sincronizar histórico inicial
|
|
7
|
+
- capturar ticks em tempo real
|
|
8
|
+
- persistir ticks via TickRepository
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
import threading
|
|
13
|
+
import MetaTrader5 as mt5
|
|
14
|
+
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from mtcli.mt5_context import mt5_conexao
|
|
18
|
+
from .tick_repository import TickRepository
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TickEngine:
|
|
22
|
+
|
|
23
|
+
POLL_INTERVAL = 0.2
|
|
24
|
+
BATCH_SIZE = 1000
|
|
25
|
+
OVERLAP_MS = 5
|
|
26
|
+
|
|
27
|
+
def __init__(self, symbols):
|
|
28
|
+
|
|
29
|
+
self.symbols = symbols
|
|
30
|
+
|
|
31
|
+
self.repositories = {
|
|
32
|
+
symbol: TickRepository()
|
|
33
|
+
for symbol in symbols
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
self.running = False
|
|
37
|
+
self.thread = None
|
|
38
|
+
|
|
39
|
+
def start(self):
|
|
40
|
+
|
|
41
|
+
if self.running:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
self.running = True
|
|
45
|
+
|
|
46
|
+
self.thread = threading.Thread(
|
|
47
|
+
target=self._run,
|
|
48
|
+
daemon=True,
|
|
49
|
+
name="mtcli-tick-engine",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.thread.start()
|
|
53
|
+
|
|
54
|
+
def stop(self):
|
|
55
|
+
|
|
56
|
+
self.running = False
|
|
57
|
+
|
|
58
|
+
if self.thread:
|
|
59
|
+
self.thread.join()
|
|
60
|
+
|
|
61
|
+
def _run(self):
|
|
62
|
+
|
|
63
|
+
with mt5_conexao():
|
|
64
|
+
|
|
65
|
+
last_positions = {}
|
|
66
|
+
|
|
67
|
+
for symbol in self.symbols:
|
|
68
|
+
|
|
69
|
+
repo = self.repositories[symbol]
|
|
70
|
+
|
|
71
|
+
# sincroniza histórico
|
|
72
|
+
repo.sync(symbol)
|
|
73
|
+
|
|
74
|
+
last_msc = repo._get_last_tick_msc(symbol)
|
|
75
|
+
|
|
76
|
+
if last_msc:
|
|
77
|
+
last_positions[symbol] = last_msc
|
|
78
|
+
else:
|
|
79
|
+
last_positions[symbol] = int(time.time() * 1000)
|
|
80
|
+
|
|
81
|
+
while self.running:
|
|
82
|
+
|
|
83
|
+
for symbol in self.symbols:
|
|
84
|
+
self._drain_symbol(symbol, last_positions)
|
|
85
|
+
|
|
86
|
+
time.sleep(self.POLL_INTERVAL)
|
|
87
|
+
|
|
88
|
+
def _drain_symbol(self, symbol, last_positions):
|
|
89
|
+
|
|
90
|
+
repo = self.repositories[symbol]
|
|
91
|
+
|
|
92
|
+
last_msc = last_positions[symbol]
|
|
93
|
+
|
|
94
|
+
start_dt = datetime.fromtimestamp(
|
|
95
|
+
(last_msc - self.OVERLAP_MS) / 1000
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
while True:
|
|
99
|
+
|
|
100
|
+
ticks = mt5.copy_ticks_from(
|
|
101
|
+
symbol,
|
|
102
|
+
start_dt,
|
|
103
|
+
self.BATCH_SIZE,
|
|
104
|
+
mt5.COPY_TICKS_ALL,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if ticks is None or len(ticks) == 0:
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
repo.conn.execute("BEGIN")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
|
|
114
|
+
repo._insert_ticks(symbol, ticks)
|
|
115
|
+
|
|
116
|
+
repo.cache.add_many(ticks)
|
|
117
|
+
|
|
118
|
+
repo.conn.commit()
|
|
119
|
+
|
|
120
|
+
except Exception:
|
|
121
|
+
|
|
122
|
+
repo.conn.rollback()
|
|
123
|
+
raise
|
|
124
|
+
|
|
125
|
+
last_msc = int(ticks[-1]["time_msc"])
|
|
126
|
+
|
|
127
|
+
last_positions[symbol] = last_msc + 1
|
|
128
|
+
|
|
129
|
+
start_dt = datetime.fromtimestamp(
|
|
130
|
+
(last_msc - self.OVERLAP_MS) / 1000
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if len(ticks) < self.BATCH_SIZE:
|
|
134
|
+
break
|
|
@@ -3,12 +3,13 @@ TickRepository.
|
|
|
3
3
|
|
|
4
4
|
Responsável por:
|
|
5
5
|
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
6
|
+
- persistência de ticks
|
|
7
|
+
- sincronização histórica
|
|
8
|
+
- consultas rápidas
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import MetaTrader5 as mt5
|
|
12
|
+
|
|
12
13
|
from datetime import datetime, timedelta
|
|
13
14
|
|
|
14
15
|
from ..database import get_connection, backup_database
|
|
@@ -18,7 +19,7 @@ from mtcli.mt5_context import mt5_conexao
|
|
|
18
19
|
|
|
19
20
|
class TickRepository:
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
RANGE_WINDOW_MINUTES = 10
|
|
22
23
|
|
|
23
24
|
def __init__(self):
|
|
24
25
|
|
|
@@ -28,64 +29,64 @@ class TickRepository:
|
|
|
28
29
|
self.last_backup_day = None
|
|
29
30
|
|
|
30
31
|
# ==========================================================
|
|
31
|
-
#
|
|
32
|
+
# SYNC HISTÓRICO
|
|
32
33
|
# ==========================================================
|
|
33
34
|
|
|
34
35
|
def sync(self, symbol: str, days_back: int = 1):
|
|
35
36
|
|
|
36
37
|
total_inserted = 0
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
end = datetime.now()
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
last_msc = self._get_last_tick_msc(symbol)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if last_msc:
|
|
44
|
+
start = datetime.fromtimestamp((last_msc + 1) / 1000)
|
|
45
|
+
else:
|
|
46
|
+
start = end - timedelta(days=days_back)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
window = timedelta(minutes=self.RANGE_WINDOW_MINUTES)
|
|
49
|
+
|
|
50
|
+
with mt5_conexao():
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
while start < end:
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
chunk_end = min(start + window, end)
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
ticks = mt5.copy_ticks_range(
|
|
57
|
+
symbol,
|
|
58
|
+
start,
|
|
59
|
+
chunk_end,
|
|
60
|
+
mt5.COPY_TICKS_ALL
|
|
61
|
+
)
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
break
|
|
63
|
+
if ticks is not None and len(ticks) > 0:
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
self.conn.execute("BEGIN")
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
try:
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
inserted = self._insert_ticks(symbol, ticks)
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
total_inserted += inserted
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
self.cache.add_many(ticks)
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
break
|
|
75
|
+
self.conn.commit()
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
except Exception:
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
self.conn.rollback()
|
|
80
|
+
raise
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
raise
|
|
82
|
+
start = chunk_end
|
|
82
83
|
|
|
83
84
|
self._daily_backup()
|
|
84
85
|
|
|
85
86
|
return total_inserted
|
|
86
87
|
|
|
87
88
|
# ==========================================================
|
|
88
|
-
#
|
|
89
|
+
# INSERT
|
|
89
90
|
# ==========================================================
|
|
90
91
|
|
|
91
92
|
def _insert_ticks(self, symbol, ticks):
|
|
@@ -112,7 +113,7 @@ class TickRepository:
|
|
|
112
113
|
symbol,time,time_msc,bid,ask,last,volume,flags
|
|
113
114
|
)
|
|
114
115
|
VALUES (?,?,?,?,?,?,?,?)
|
|
115
|
-
ON CONFLICT(symbol,
|
|
116
|
+
ON CONFLICT(symbol,time_msc) DO NOTHING
|
|
116
117
|
""",
|
|
117
118
|
data,
|
|
118
119
|
)
|
|
@@ -162,7 +163,7 @@ class TickRepository:
|
|
|
162
163
|
return cursor.fetchall()
|
|
163
164
|
|
|
164
165
|
# ==========================================================
|
|
165
|
-
#
|
|
166
|
+
# UTIL
|
|
166
167
|
# ==========================================================
|
|
167
168
|
|
|
168
169
|
def _get_last_tick_msc(self, symbol):
|
|
@@ -178,9 +179,9 @@ class TickRepository:
|
|
|
178
179
|
(symbol,),
|
|
179
180
|
)
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
row = cursor.fetchone()
|
|
182
183
|
|
|
183
|
-
return
|
|
184
|
+
return row[0] if row and row[0] else None
|
|
184
185
|
|
|
185
186
|
def _daily_backup(self):
|
|
186
187
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
def upgrade(conn):
|
|
2
|
+
|
|
3
|
+
cursor = conn.execute("""
|
|
4
|
+
SELECT sql
|
|
5
|
+
FROM sqlite_master
|
|
6
|
+
WHERE type='table'
|
|
7
|
+
AND name='ticks'
|
|
8
|
+
""")
|
|
9
|
+
|
|
10
|
+
row = cursor.fetchone()
|
|
11
|
+
|
|
12
|
+
if not row:
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
if "PRIMARY KEY(symbol, time_msc)" in row[0]:
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
print("Rebuilding ticks table with PK(symbol,time_msc)...")
|
|
19
|
+
|
|
20
|
+
conn.execute("PRAGMA foreign_keys=OFF")
|
|
21
|
+
|
|
22
|
+
conn.execute("""
|
|
23
|
+
CREATE TABLE ticks_new(
|
|
24
|
+
symbol TEXT NOT NULL,
|
|
25
|
+
time INTEGER NOT NULL,
|
|
26
|
+
time_msc INTEGER NOT NULL,
|
|
27
|
+
bid REAL,
|
|
28
|
+
ask REAL,
|
|
29
|
+
last REAL,
|
|
30
|
+
volume REAL,
|
|
31
|
+
flags INTEGER,
|
|
32
|
+
PRIMARY KEY(symbol, time_msc)
|
|
33
|
+
) WITHOUT ROWID
|
|
34
|
+
""")
|
|
35
|
+
|
|
36
|
+
conn.execute("""
|
|
37
|
+
INSERT INTO ticks_new
|
|
38
|
+
SELECT
|
|
39
|
+
symbol,
|
|
40
|
+
time,
|
|
41
|
+
COALESCE(time_msc,time),
|
|
42
|
+
bid,
|
|
43
|
+
ask,
|
|
44
|
+
last,
|
|
45
|
+
volume,
|
|
46
|
+
flags
|
|
47
|
+
FROM ticks
|
|
48
|
+
""")
|
|
49
|
+
|
|
50
|
+
conn.execute("DROP TABLE ticks")
|
|
51
|
+
|
|
52
|
+
conn.execute("ALTER TABLE ticks_new RENAME TO ticks")
|
|
53
|
+
|
|
54
|
+
conn.commit()
|
|
55
|
+
|
|
56
|
+
print("ticks table rebuilt with PK(symbol,time_msc)")
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Motor de captura de ticks multi-ativo.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
import threading
|
|
7
|
-
import MetaTrader5 as mt5
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
|
|
10
|
-
from mtcli.mt5_context import mt5_conexao
|
|
11
|
-
from .tick_repository import TickRepository
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TickEngine:
|
|
15
|
-
|
|
16
|
-
POLL_INTERVAL = 0.2
|
|
17
|
-
|
|
18
|
-
def __init__(self, symbols):
|
|
19
|
-
|
|
20
|
-
self.symbols = symbols
|
|
21
|
-
|
|
22
|
-
self.repositories = {
|
|
23
|
-
symbol: TickRepository()
|
|
24
|
-
for symbol in symbols
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
self.running = False
|
|
28
|
-
|
|
29
|
-
def start(self):
|
|
30
|
-
|
|
31
|
-
thread = threading.Thread(
|
|
32
|
-
target=self._run,
|
|
33
|
-
daemon=True
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
self.running = True
|
|
37
|
-
thread.start()
|
|
38
|
-
|
|
39
|
-
def stop(self):
|
|
40
|
-
|
|
41
|
-
self.running = False
|
|
42
|
-
|
|
43
|
-
def _run(self):
|
|
44
|
-
|
|
45
|
-
with mt5_conexao():
|
|
46
|
-
|
|
47
|
-
last_positions = {}
|
|
48
|
-
|
|
49
|
-
for symbol in self.symbols:
|
|
50
|
-
|
|
51
|
-
repo = self.repositories[symbol]
|
|
52
|
-
last_msc = repo._get_last_tick_msc(symbol)
|
|
53
|
-
|
|
54
|
-
if last_msc:
|
|
55
|
-
last_positions[symbol] = last_msc + 1
|
|
56
|
-
else:
|
|
57
|
-
last_positions[symbol] = int(time.time() * 1000)
|
|
58
|
-
|
|
59
|
-
while self.running:
|
|
60
|
-
|
|
61
|
-
for symbol in self.symbols:
|
|
62
|
-
|
|
63
|
-
repo = self.repositories[symbol]
|
|
64
|
-
|
|
65
|
-
start = last_positions[symbol]
|
|
66
|
-
|
|
67
|
-
ticks = mt5.copy_ticks_from(
|
|
68
|
-
symbol,
|
|
69
|
-
datetime.fromtimestamp(start / 1000),
|
|
70
|
-
1000,
|
|
71
|
-
mt5.COPY_TICKS_ALL
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
if ticks is None or len(ticks) == 0:
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
repo.conn.execute("BEGIN")
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
|
|
81
|
-
repo._insert_ticks(symbol, ticks)
|
|
82
|
-
|
|
83
|
-
repo.cache.add_many(ticks)
|
|
84
|
-
|
|
85
|
-
repo.conn.commit()
|
|
86
|
-
|
|
87
|
-
except Exception:
|
|
88
|
-
|
|
89
|
-
repo.conn.rollback()
|
|
90
|
-
|
|
91
|
-
last_positions[symbol] = int(ticks[-1]["time_msc"]) + 1
|
|
92
|
-
|
|
93
|
-
time.sleep(self.POLL_INTERVAL)
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Captura contínua de ticks.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
import MetaTrader5 as mt5
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
|
|
9
|
-
from mtcli.mt5_context import mt5_conexao
|
|
10
|
-
from .tick_repository import TickRepository
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TickStreamer:
|
|
14
|
-
|
|
15
|
-
def __init__(self, symbol):
|
|
16
|
-
|
|
17
|
-
self.symbol = symbol
|
|
18
|
-
self.repo = TickRepository()
|
|
19
|
-
self.running = False
|
|
20
|
-
|
|
21
|
-
def start(self):
|
|
22
|
-
|
|
23
|
-
self.running = True
|
|
24
|
-
|
|
25
|
-
with mt5_conexao():
|
|
26
|
-
|
|
27
|
-
last_msc = self.repo._get_last_tick_msc(self.symbol)
|
|
28
|
-
|
|
29
|
-
if last_msc:
|
|
30
|
-
start = last_msc + 1
|
|
31
|
-
else:
|
|
32
|
-
start = int(time.time() * 1000)
|
|
33
|
-
|
|
34
|
-
while self.running:
|
|
35
|
-
|
|
36
|
-
ticks = mt5.copy_ticks_from(
|
|
37
|
-
self.symbol,
|
|
38
|
-
datetime.fromtimestamp(start / 1000),
|
|
39
|
-
1000,
|
|
40
|
-
mt5.COPY_TICKS_ALL,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
if ticks is None or len(ticks) == 0:
|
|
44
|
-
|
|
45
|
-
time.sleep(0.1)
|
|
46
|
-
continue
|
|
47
|
-
|
|
48
|
-
self.repo.conn.execute("BEGIN")
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
|
|
52
|
-
self.repo._insert_ticks(self.symbol, ticks)
|
|
53
|
-
|
|
54
|
-
self.repo.cache.add_many(ticks)
|
|
55
|
-
|
|
56
|
-
self.repo.conn.commit()
|
|
57
|
-
|
|
58
|
-
except Exception:
|
|
59
|
-
|
|
60
|
-
self.repo.conn.rollback()
|
|
61
|
-
|
|
62
|
-
start = int(ticks[-1]["time_msc"]) + 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/media_movel/tests/test_model_media_movel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/range_medio/models/average_range_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mtcli-3.8.0.dev4 → mtcli-3.8.0.dev5}/mtcli/plugins/volume_medio/models/model_average_volume.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|