mtcli 3.5.0.dev3__tar.gz → 3.6.0__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.5.0.dev3 → mtcli-3.6.0}/PKG-INFO +1 -1
- mtcli-3.6.0/mtcli/cli.py +34 -0
- mtcli-3.6.0/mtcli/conf.py +199 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/data/csv.py +1 -1
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/database.py +54 -54
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/marketdata/tick_cache.py +24 -24
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/marketdata/tick_repository.py +142 -142
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/bar_model.py +105 -105
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/consecutive_bars_model.py +77 -77
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/unconsecutive_bar_model.py +67 -67
- mtcli-3.6.0/mtcli/plugin.py +18 -0
- mtcli-3.6.0/mtcli/plugin_loader.py +97 -0
- mtcli-3.6.0/mtcli/plugins/exemplo.py-dist +30 -0
- mtcli-3.6.0/mtcli/plugins/media_movel/__init__.py +5 -0
- mtcli-3.5.0.dev3/mtcli/plugins/media_movel/command.py → mtcli-3.6.0/mtcli/plugins/media_movel/cli.py +1 -2
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/tests/test_mm.py +13 -13
- mtcli-3.6.0/mtcli/plugins/range_medio/__init__.py +5 -0
- mtcli-3.5.0.dev3/mtcli/plugins/range_medio/command.py → mtcli-3.6.0/mtcli/plugins/range_medio/cli.py +32 -33
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/range_medio/models/average_range_model.py +29 -29
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/range_medio/tests/test_rm.py +1 -1
- mtcli-3.6.0/mtcli/plugins/volume_medio/__init__.py +5 -0
- mtcli-3.5.0.dev3/mtcli/plugins/volume_medio/command.py → mtcli-3.6.0/mtcli/plugins/volume_medio/cli.py +41 -42
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/volume_medio/models/model_average_volume.py +31 -31
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/volume_medio/tests/test_vm.py +1 -1
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/close_view.py +37 -37
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/full_view.py +65 -65
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/high_view.py +37 -37
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/low_view.py +37 -37
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/min_view.py +42 -42
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/open_view.py +37 -37
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/ranges_view.py +41 -41
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/rates_view.py +41 -41
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/pyproject.toml +3 -5
- mtcli-3.5.0.dev3/mtcli/commands/conf.py +0 -50
- mtcli-3.5.0.dev3/mtcli/commands/logs.py +0 -42
- mtcli-3.5.0.dev3/mtcli/conf.py +0 -102
- mtcli-3.5.0.dev3/mtcli/mt.py +0 -45
- mtcli-3.5.0.dev3/mtcli/plugin.py +0 -9
- mtcli-3.5.0.dev3/mtcli/plugins/media_movel/__init__.py +0 -3
- mtcli-3.5.0.dev3/mtcli/plugins/range_medio/__init__.py +0 -2
- mtcli-3.5.0.dev3/mtcli/plugins/volume_medio/__init__.py +0 -3
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/LICENSE +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/README.md +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/commands/bars.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/conecta.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/data/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/data/base.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/data/mt5.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/domain/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/logger.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/marketdata/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/mt5_context.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/tests/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/range_medio/models/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/range_medio/tests/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/__init__.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.5.0.dev3 → mtcli-3.6.0}/mtcli/views/volumes_view.py +0 -0
mtcli-3.6.0/mtcli/cli.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI principal do mtcli.
|
|
3
|
+
|
|
4
|
+
Este módulo define o grupo principal `mt`
|
|
5
|
+
e inicializa o carregamento de plugins.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from mtcli.plugin_loader import load_plugins
|
|
11
|
+
from .commands.bars import bars
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group(context_settings={"max_content_width": 120})
|
|
15
|
+
@click.version_option(package_name="mtcli")
|
|
16
|
+
def mt():
|
|
17
|
+
"""
|
|
18
|
+
CLI principal do mtcli.
|
|
19
|
+
|
|
20
|
+
Exibe gráficos e informações de mercado
|
|
21
|
+
em formato textual compatível com leitores de tela.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
mt.add_command(bars, name="bars")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Carrega plugins automaticamente
|
|
30
|
+
load_plugins(mt)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
mt()
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configurações principais do mtcli.
|
|
3
|
+
|
|
4
|
+
Este módulo centraliza a leitura de configurações provenientes de:
|
|
5
|
+
|
|
6
|
+
1. Variáveis de ambiente
|
|
7
|
+
2. Arquivo mtcli.ini
|
|
8
|
+
|
|
9
|
+
As variáveis de ambiente sempre têm prioridade sobre o arquivo INI.
|
|
10
|
+
|
|
11
|
+
Também fornece utilidades para obter o caminho de arquivos do
|
|
12
|
+
terminal MetaTrader5 e selecionar a fonte de dados (CSV ou MT5).
|
|
13
|
+
|
|
14
|
+
O acesso ao terminal MT5 é realizado apenas sob demanda para evitar
|
|
15
|
+
efeitos colaterais durante a importação do módulo (import side effects),
|
|
16
|
+
facilitando testes automatizados e execução em ambientes CI/CD.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import configparser
|
|
21
|
+
|
|
22
|
+
import MetaTrader5 as mt5
|
|
23
|
+
|
|
24
|
+
from mtcli.mt5_context import mt5_conexao
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------
|
|
27
|
+
# Carregamento do arquivo de configuração
|
|
28
|
+
# ---------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
CONFIG_FILE = "mtcli.ini"
|
|
31
|
+
SECTION = "DEFAULT"
|
|
32
|
+
|
|
33
|
+
config = configparser.ConfigParser()
|
|
34
|
+
config.read(CONFIG_FILE)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_config_value(key: str, cast=None, fallback=None):
|
|
38
|
+
"""
|
|
39
|
+
Retorna um valor de configuração.
|
|
40
|
+
|
|
41
|
+
A prioridade de leitura é:
|
|
42
|
+
1. Variável de ambiente
|
|
43
|
+
2. Arquivo mtcli.ini
|
|
44
|
+
3. Valor fallback
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
key (str): Nome da chave de configuração.
|
|
48
|
+
cast (type | None): Tipo de conversão (ex: int, float).
|
|
49
|
+
fallback (Any): Valor padrão caso não encontrado.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Any: valor convertido ou fallback.
|
|
53
|
+
"""
|
|
54
|
+
value = os.getenv(key)
|
|
55
|
+
|
|
56
|
+
if value is None:
|
|
57
|
+
try:
|
|
58
|
+
if cast == int:
|
|
59
|
+
value = config.getint(SECTION, key, fallback=fallback)
|
|
60
|
+
elif cast == float:
|
|
61
|
+
value = config.getfloat(SECTION, key, fallback=fallback)
|
|
62
|
+
else:
|
|
63
|
+
value = config.get(SECTION, key, fallback=fallback)
|
|
64
|
+
except (configparser.NoOptionError, ValueError):
|
|
65
|
+
value = fallback
|
|
66
|
+
else:
|
|
67
|
+
if cast:
|
|
68
|
+
try:
|
|
69
|
+
value = cast(value)
|
|
70
|
+
except ValueError:
|
|
71
|
+
value = fallback
|
|
72
|
+
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------
|
|
77
|
+
# Configurações gerais
|
|
78
|
+
# ---------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
SYMBOL = get_config_value("symbol", fallback="WIN$N")
|
|
81
|
+
DIGITOS = get_config_value("digitos", cast=int, fallback=2)
|
|
82
|
+
PERIOD = get_config_value("period", fallback="D1")
|
|
83
|
+
BARS = get_config_value("count", cast=int, fallback=999)
|
|
84
|
+
|
|
85
|
+
VIEW = get_config_value("view", fallback="ch")
|
|
86
|
+
VOLUME = get_config_value("volume", fallback="tick")
|
|
87
|
+
DATE = get_config_value("date", fallback="")
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------
|
|
90
|
+
# Configurações de leitura de candles
|
|
91
|
+
# ---------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
LATERAL = get_config_value("lateral", fallback="doji")
|
|
94
|
+
ALTA = get_config_value("alta", fallback="verde")
|
|
95
|
+
BAIXA = get_config_value("baixa", fallback="vermelho")
|
|
96
|
+
|
|
97
|
+
ROMPIMENTO_ALTA = get_config_value("rompimento_alta", fallback="c")
|
|
98
|
+
ROMPIMENTO_BAIXA = get_config_value("rompimento_baixa", fallback="v")
|
|
99
|
+
|
|
100
|
+
PERCENTUAL_ROMPIMENTO = get_config_value(
|
|
101
|
+
"percentual_rompimento", cast=int, fallback=50
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
PERCENTUAL_DOJI = get_config_value(
|
|
105
|
+
"percentual_doji", cast=int, fallback=10
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------
|
|
109
|
+
# Configurações de padrões de barra
|
|
110
|
+
# ---------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
UP_BAR = get_config_value("up_bar", fallback="asc")
|
|
113
|
+
DOWN_BAR = get_config_value("down_bar", fallback="desc")
|
|
114
|
+
|
|
115
|
+
INSIDE_BAR = get_config_value("inside_bar", fallback="ib")
|
|
116
|
+
OUTSIDE_BAR = get_config_value("outside_bar", fallback="ob")
|
|
117
|
+
|
|
118
|
+
SOMBRA_SUPERIOR = get_config_value("sombra_superior", fallback="top")
|
|
119
|
+
SOMBRA_INFERIOR = get_config_value("sombra_inferior", fallback="bottom")
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------
|
|
122
|
+
# Fonte de dados
|
|
123
|
+
# ---------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
DATA_SOURCE = get_config_value("dados", fallback="mt5").lower()
|
|
126
|
+
|
|
127
|
+
# caminho inicial (pode vir do ini/env)
|
|
128
|
+
_INITIAL_CSV_PATH = get_config_value("mt5_pasta", fallback="")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_csv_path():
|
|
132
|
+
"""
|
|
133
|
+
Retorna o caminho da pasta de arquivos do MT5 ou CSV.
|
|
134
|
+
|
|
135
|
+
Se o caminho não estiver definido no mtcli.ini ou nas
|
|
136
|
+
variáveis de ambiente, o terminal MT5 é consultado para
|
|
137
|
+
descobrir automaticamente a pasta MQL5/Files.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
str: caminho normalizado da pasta de arquivos.
|
|
141
|
+
"""
|
|
142
|
+
if _INITIAL_CSV_PATH:
|
|
143
|
+
path = _INITIAL_CSV_PATH
|
|
144
|
+
else:
|
|
145
|
+
with mt5_conexao():
|
|
146
|
+
terminal_info = mt5.terminal_info()
|
|
147
|
+
|
|
148
|
+
if terminal_info is None:
|
|
149
|
+
raise RuntimeError(
|
|
150
|
+
"Não foi possível obter as informações do terminal MT5."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
path = os.path.join(terminal_info.data_path, "MQL5", "Files")
|
|
154
|
+
|
|
155
|
+
return os.path.normpath(path) + os.sep
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------
|
|
159
|
+
# Factory de DataSource
|
|
160
|
+
# ---------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def get_data_source(source=None):
|
|
163
|
+
"""
|
|
164
|
+
Retorna a fonte de dados configurada.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
source (str | None): sobrescreve DATA_SOURCE se fornecido.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
CsvDataSource | MT5DataSource
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: se a fonte de dados não for reconhecida.
|
|
174
|
+
"""
|
|
175
|
+
from mtcli.data import CsvDataSource, MT5DataSource
|
|
176
|
+
|
|
177
|
+
src = source.lower() if source else DATA_SOURCE
|
|
178
|
+
|
|
179
|
+
if src == "csv":
|
|
180
|
+
return CsvDataSource()
|
|
181
|
+
|
|
182
|
+
if src == "mt5":
|
|
183
|
+
return MT5DataSource()
|
|
184
|
+
|
|
185
|
+
raise ValueError(f"Fonte de dados desconhecida: {src}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------
|
|
189
|
+
# Timeframes suportados
|
|
190
|
+
# ---------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
_HOURS = [12, 8, 6, 4, 3, 2, 1]
|
|
193
|
+
_MINUTES = [30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1]
|
|
194
|
+
|
|
195
|
+
TIMEFRAMES = (
|
|
196
|
+
["mn1", "w1", "d1"]
|
|
197
|
+
+ [f"h{i}" for i in _HOURS]
|
|
198
|
+
+ [f"m{i}" for i in _MINUTES]
|
|
199
|
+
)
|
|
@@ -16,7 +16,7 @@ class CsvDataSource(DataSourceBase):
|
|
|
16
16
|
|
|
17
17
|
def get_data(self, symbol, period, count=100):
|
|
18
18
|
"""Retorna dados CSV em uma lista de lista."""
|
|
19
|
-
file_path = os.path.join(conf.
|
|
19
|
+
file_path = os.path.join(conf._INITIAL_CSV_PATH, f"{symbol}{period}.csv")
|
|
20
20
|
logger.info(f"Iniciando coleta de dados via CSV: {file_path}.")
|
|
21
21
|
csv_data = []
|
|
22
22
|
try:
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Database core para mtcli.
|
|
3
|
-
|
|
4
|
-
Responsável por:
|
|
5
|
-
- Criar conexão SQLite
|
|
6
|
-
- Ativar WAL
|
|
7
|
-
- Garantir schema
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import sqlite3
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
DB_PATH = Path.home() / ".mtcli" / "marketdata.db"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_connection():
|
|
18
|
-
"""
|
|
19
|
-
Retorna conexão SQLite configurada.
|
|
20
|
-
"""
|
|
21
|
-
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
-
|
|
23
|
-
conn = sqlite3.connect(DB_PATH)
|
|
24
|
-
conn.execute("PRAGMA journal_mode=WAL;")
|
|
25
|
-
conn.execute("PRAGMA synchronous=NORMAL;")
|
|
26
|
-
|
|
27
|
-
_ensure_schema(conn)
|
|
28
|
-
return conn
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _ensure_schema(conn):
|
|
32
|
-
conn.execute(
|
|
33
|
-
"""
|
|
34
|
-
CREATE TABLE IF NOT EXISTS ticks (
|
|
35
|
-
symbol TEXT NOT NULL,
|
|
36
|
-
time INTEGER NOT NULL,
|
|
37
|
-
bid REAL,
|
|
38
|
-
ask REAL,
|
|
39
|
-
last REAL,
|
|
40
|
-
volume REAL,
|
|
41
|
-
flags INTEGER,
|
|
42
|
-
PRIMARY KEY (symbol, time)
|
|
43
|
-
);
|
|
44
|
-
"""
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
conn.execute(
|
|
48
|
-
"""
|
|
49
|
-
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
50
|
-
ON ticks(symbol, time);
|
|
51
|
-
"""
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
conn.commit()
|
|
1
|
+
"""
|
|
2
|
+
Database core para mtcli.
|
|
3
|
+
|
|
4
|
+
Responsável por:
|
|
5
|
+
- Criar conexão SQLite
|
|
6
|
+
- Ativar WAL
|
|
7
|
+
- Garantir schema
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sqlite3
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DB_PATH = Path.home() / ".mtcli" / "marketdata.db"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_connection():
|
|
18
|
+
"""
|
|
19
|
+
Retorna conexão SQLite configurada.
|
|
20
|
+
"""
|
|
21
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
conn = sqlite3.connect(DB_PATH)
|
|
24
|
+
conn.execute("PRAGMA journal_mode=WAL;")
|
|
25
|
+
conn.execute("PRAGMA synchronous=NORMAL;")
|
|
26
|
+
|
|
27
|
+
_ensure_schema(conn)
|
|
28
|
+
return conn
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _ensure_schema(conn):
|
|
32
|
+
conn.execute(
|
|
33
|
+
"""
|
|
34
|
+
CREATE TABLE IF NOT EXISTS ticks (
|
|
35
|
+
symbol TEXT NOT NULL,
|
|
36
|
+
time INTEGER NOT NULL,
|
|
37
|
+
bid REAL,
|
|
38
|
+
ask REAL,
|
|
39
|
+
last REAL,
|
|
40
|
+
volume REAL,
|
|
41
|
+
flags INTEGER,
|
|
42
|
+
PRIMARY KEY (symbol, time)
|
|
43
|
+
);
|
|
44
|
+
"""
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
conn.execute(
|
|
48
|
+
"""
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
50
|
+
ON ticks(symbol, time);
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
conn.commit()
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Cache de ticks em memória.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from collections import deque
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TickCache:
|
|
9
|
-
"""
|
|
10
|
-
Mantém janela recente de ticks em memória.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, max_size=10000):
|
|
14
|
-
self.buffer = deque(maxlen=max_size)
|
|
15
|
-
|
|
16
|
-
def add_many(self, ticks):
|
|
17
|
-
for t in ticks:
|
|
18
|
-
self.buffer.append(t)
|
|
19
|
-
|
|
20
|
-
def get_all(self):
|
|
21
|
-
return list(self.buffer)
|
|
22
|
-
|
|
23
|
-
def clear(self):
|
|
24
|
-
self.buffer.clear()
|
|
1
|
+
"""
|
|
2
|
+
Cache de ticks em memória.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections import deque
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TickCache:
|
|
9
|
+
"""
|
|
10
|
+
Mantém janela recente de ticks em memória.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, max_size=10000):
|
|
14
|
+
self.buffer = deque(maxlen=max_size)
|
|
15
|
+
|
|
16
|
+
def add_many(self, ticks):
|
|
17
|
+
for t in ticks:
|
|
18
|
+
self.buffer.append(t)
|
|
19
|
+
|
|
20
|
+
def get_all(self):
|
|
21
|
+
return list(self.buffer)
|
|
22
|
+
|
|
23
|
+
def clear(self):
|
|
24
|
+
self.buffer.clear()
|