mtcli 3.8.0.dev7__tar.gz → 3.8.0.dev9__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.dev7 → mtcli-3.8.0.dev9}/PKG-INFO +1 -1
- mtcli-3.8.0.dev9/mtcli/__main__.py +3 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/cli.py +0 -2
- mtcli-3.8.0.dev9/mtcli/cli_dev.py +8 -0
- mtcli-3.8.0.dev9/mtcli/commands_dev/migrate.py +48 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/database.py +1 -1
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/marketdata/tick_repository.py +2 -3
- mtcli-3.8.0.dev9/mtcli/migrations/001_initial_schema.py +51 -0
- mtcli-3.8.0.dev9/mtcli/migrations/002_ticks_time_msc.py +53 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/migrations/003_optimize_ticks_without_rowid.py +23 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/migrations/004_ticks_pk_time_msc.py +22 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/migrations/005_tick_price_compression.py +27 -0
- mtcli-3.8.0.dev9/mtcli/migrations/006_add_index_ticks_symbol_time.py +32 -0
- mtcli-3.8.0.dev9/mtcli/migrations/007_deduplicate_ticks_and_add_unique_index.py +91 -0
- mtcli-3.8.0.dev9/mtcli/migrations/__main__.py +7 -0
- mtcli-3.8.0.dev9/mtcli/migrations/runner.py +245 -0
- mtcli-3.8.0.dev9/mtcli/plugins/volume_medio/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/pyproject.toml +2 -1
- mtcli-3.8.0.dev7/mtcli/commands/migrate.py +0 -12
- mtcli-3.8.0.dev7/mtcli/migrations/001_initial_schema.py +0 -21
- mtcli-3.8.0.dev7/mtcli/migrations/002_ticks_time_msc.py +0 -21
- mtcli-3.8.0.dev7/mtcli/migrations/runner.py +0 -104
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/LICENSE +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/README.md +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/commands/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/commands/bars.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/commands/conf.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/commands/doctor.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/commands/ticks.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/domain → mtcli-3.8.0.dev9/mtcli/commands_dev}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/conecta.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/conf.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/config_registre.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/data/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/data/base.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/data/csv.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/data/mt5.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/marketdata → mtcli-3.8.0.dev9/mtcli/domain}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/domain/timeframe.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/logger.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/migrations → mtcli-3.8.0.dev9/mtcli/marketdata}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/marketdata/tick_cache.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/marketdata/tick_engine.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/plugins → mtcli-3.8.0.dev9/mtcli/migrations}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/bar_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/bars_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/chart_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/conf_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/consecutive_bars_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/rates_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/signals_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/models/unconsecutive_bar_model.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/mt5_context.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugin.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugin_loader.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugin_manager.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/plugins/media_movel/tests → mtcli-3.8.0.dev9/mtcli/plugins}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/exemplo.py-dist +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/cli.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/conf.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/models/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/models/model_media_movel.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/plugins/range_medio/models → mtcli-3.8.0.dev9/mtcli/plugins/media_movel/tests}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/tests/test_mm.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/media_movel/tests/test_model_media_movel.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/range_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/range_medio/cli.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/range_medio/conf.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/plugins/range_medio/tests → mtcli-3.8.0.dev9/mtcli/plugins/range_medio/models}/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/range_medio/models/average_range_model.py +0 -0
- {mtcli-3.8.0.dev7/mtcli/plugins/volume_medio → mtcli-3.8.0.dev9/mtcli/plugins/range_medio}/tests/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/range_medio/tests/test_rm.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/cli.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/conf.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/models/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/models/model_average_volume.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/plugins/volume_medio/tests/test_vm.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/__init__.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/close_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/full_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/high_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/low_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/min_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/open_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/ranges_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/rates_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/vars_view.py +0 -0
- {mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/mtcli/views/volumes_view.py +0 -0
|
@@ -27,7 +27,6 @@ from mtcli.marketdata.tick_engine import TickEngine
|
|
|
27
27
|
|
|
28
28
|
from .commands.bars import bars
|
|
29
29
|
from .commands.doctor import doctor
|
|
30
|
-
from .commands.migrate import migrate
|
|
31
30
|
from .commands.ticks import ticks
|
|
32
31
|
|
|
33
32
|
|
|
@@ -89,7 +88,6 @@ def mt(ctx):
|
|
|
89
88
|
mt.add_command(doctor, name="doctor")
|
|
90
89
|
mt.add_command(bars, name="bars")
|
|
91
90
|
mt.add_command(doctor, name="dr")
|
|
92
|
-
mt.add_command(migrate)
|
|
93
91
|
mt.add_command(ticks)
|
|
94
92
|
|
|
95
93
|
loaded_plugins = load_plugins(mt)
|
|
@@ -0,0 +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)
|
|
@@ -41,7 +41,7 @@ def get_connection():
|
|
|
41
41
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
42
42
|
conn.execute("PRAGMA synchronous=NORMAL")
|
|
43
43
|
conn.execute("PRAGMA temp_store=MEMORY")
|
|
44
|
-
conn.execute("PRAGMA mmap_size=
|
|
44
|
+
conn.execute("PRAGMA mmap_size=30000000000")
|
|
45
45
|
conn.execute("PRAGMA cache_size=-200000")
|
|
46
46
|
conn.execute("PRAGMA journal_size_limit=67108864")
|
|
47
47
|
conn.execute("PRAGMA read_uncommitted = TRUE")
|
|
@@ -114,7 +114,7 @@ class TickRepository:
|
|
|
114
114
|
|
|
115
115
|
cursor.executemany(
|
|
116
116
|
"""
|
|
117
|
-
INSERT INTO ticks(
|
|
117
|
+
INSERT OR IGNORE INTO ticks(
|
|
118
118
|
symbol,
|
|
119
119
|
time_msc,
|
|
120
120
|
bid,
|
|
@@ -124,12 +124,11 @@ class TickRepository:
|
|
|
124
124
|
flags
|
|
125
125
|
)
|
|
126
126
|
VALUES (?,?,?,?,?,?,?)
|
|
127
|
-
ON CONFLICT(symbol,time_msc) DO NOTHING
|
|
128
127
|
""",
|
|
129
128
|
data,
|
|
130
129
|
)
|
|
131
130
|
|
|
132
|
-
return
|
|
131
|
+
return cursor.rowcount
|
|
133
132
|
|
|
134
133
|
# ==========================================================
|
|
135
134
|
# CONSULTAS
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 001
|
|
3
|
+
|
|
4
|
+
Cria o schema inicial do banco de dados do mtcli.
|
|
5
|
+
|
|
6
|
+
Esta migration cria a tabela principal `ticks`, responsável por
|
|
7
|
+
armazenar os ticks de mercado capturados do MetaTrader.
|
|
8
|
+
|
|
9
|
+
Estrutura inicial:
|
|
10
|
+
|
|
11
|
+
- symbol : símbolo do ativo
|
|
12
|
+
- time : timestamp em segundos
|
|
13
|
+
- bid : preço bid
|
|
14
|
+
- ask : preço ask
|
|
15
|
+
- last : último preço negociado
|
|
16
|
+
- volume : volume do tick
|
|
17
|
+
- flags : flags fornecidas pela API MT5
|
|
18
|
+
|
|
19
|
+
Também cria o índice `idx_ticks_symbol_time` para acelerar
|
|
20
|
+
consultas por símbolo e tempo.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def upgrade(conn):
|
|
25
|
+
"""
|
|
26
|
+
Executa a migration inicial criando a tabela `ticks`
|
|
27
|
+
e o índice principal utilizado nas consultas.
|
|
28
|
+
|
|
29
|
+
A operação é idempotente graças ao uso de
|
|
30
|
+
`CREATE TABLE IF NOT EXISTS`.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
conn.execute("""
|
|
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
|
+
conn.execute("""
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
48
|
+
ON ticks(symbol, time)
|
|
49
|
+
""")
|
|
50
|
+
|
|
51
|
+
conn.commit()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 002
|
|
3
|
+
|
|
4
|
+
Adiciona a coluna `time_msc` à tabela `ticks`.
|
|
5
|
+
|
|
6
|
+
A API do MetaTrader fornece timestamps com resolução em
|
|
7
|
+
milissegundos (`time_msc`). Esta migration adiciona suporte
|
|
8
|
+
a esse campo sem quebrar compatibilidade com bancos antigos.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def column_exists(conn, table, column):
|
|
13
|
+
"""
|
|
14
|
+
Verifica se uma coluna existe em uma tabela SQLite.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
conn : sqlite3.Connection
|
|
19
|
+
Conexão com o banco de dados.
|
|
20
|
+
table : str
|
|
21
|
+
Nome da tabela.
|
|
22
|
+
column : str
|
|
23
|
+
Nome da coluna.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
bool
|
|
28
|
+
True se a coluna existir.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
cursor = conn.execute(f"PRAGMA table_info({table})")
|
|
32
|
+
|
|
33
|
+
for row in cursor.fetchall():
|
|
34
|
+
if row[1] == column:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def upgrade(conn):
|
|
41
|
+
"""
|
|
42
|
+
Adiciona a coluna `time_msc` à tabela `ticks`
|
|
43
|
+
caso ela ainda não exista.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
if not column_exists(conn, "ticks", "time_msc"):
|
|
47
|
+
|
|
48
|
+
conn.execute("""
|
|
49
|
+
ALTER TABLE ticks
|
|
50
|
+
ADD COLUMN time_msc INTEGER
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
conn.commit()
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 003
|
|
3
|
+
|
|
4
|
+
Otimiza a tabela `ticks` convertendo-a para `WITHOUT ROWID`.
|
|
5
|
+
|
|
6
|
+
Tabelas SQLite com chave primária composta podem ter melhor
|
|
7
|
+
performance e menor uso de disco utilizando `WITHOUT ROWID`.
|
|
8
|
+
|
|
9
|
+
Para aplicar esta otimização é necessário reconstruir a tabela.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
def upgrade(conn):
|
|
14
|
+
"""
|
|
15
|
+
Reconstrói a tabela `ticks` utilizando `WITHOUT ROWID`.
|
|
16
|
+
|
|
17
|
+
Processo executado:
|
|
18
|
+
|
|
19
|
+
1. Cria nova tabela otimizada
|
|
20
|
+
2. Copia os dados da tabela antiga
|
|
21
|
+
3. Remove tabela antiga
|
|
22
|
+
4. Renomeia nova tabela
|
|
23
|
+
5. Recria índices necessários
|
|
24
|
+
"""
|
|
2
25
|
|
|
3
26
|
cursor = conn.execute("""
|
|
4
27
|
SELECT sql
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 004
|
|
3
|
+
|
|
4
|
+
Reconstrói a tabela `ticks` utilizando `time_msc` como parte
|
|
5
|
+
da chave primária.
|
|
6
|
+
|
|
7
|
+
Motivação:
|
|
8
|
+
|
|
9
|
+
- `time` possui resolução em segundos
|
|
10
|
+
- `time_msc` possui resolução em milissegundos
|
|
11
|
+
- múltiplos ticks podem ocorrer dentro do mesmo segundo
|
|
12
|
+
|
|
13
|
+
Nova chave primária:
|
|
14
|
+
|
|
15
|
+
PRIMARY KEY(symbol, time_msc)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
def upgrade(conn):
|
|
20
|
+
"""
|
|
21
|
+
Reconstrói a tabela `ticks` alterando a chave primária
|
|
22
|
+
para `(symbol, time_msc)`.
|
|
23
|
+
"""
|
|
2
24
|
|
|
3
25
|
cursor = conn.execute("""
|
|
4
26
|
SELECT sql
|
|
@@ -1,4 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 005
|
|
3
|
+
|
|
4
|
+
Aplica compressão de preços nos ticks.
|
|
5
|
+
|
|
6
|
+
Preços e volume passam a ser armazenados como inteiros
|
|
7
|
+
para reduzir uso de espaço em disco.
|
|
8
|
+
|
|
9
|
+
Transformações aplicadas:
|
|
10
|
+
|
|
11
|
+
bid -> INTEGER (bid * 100)
|
|
12
|
+
ask -> INTEGER (ask * 100)
|
|
13
|
+
last -> INTEGER (last * 100)
|
|
14
|
+
volume -> INTEGER
|
|
15
|
+
|
|
16
|
+
A tabela precisa ser reconstruída para alterar os tipos
|
|
17
|
+
das colunas.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
def upgrade(conn):
|
|
22
|
+
"""
|
|
23
|
+
Reconstrói a tabela `ticks` aplicando compressão
|
|
24
|
+
de preços e volumes.
|
|
25
|
+
|
|
26
|
+
Isso reduz significativamente o tamanho do banco
|
|
27
|
+
e melhora performance de leitura.
|
|
28
|
+
"""
|
|
2
29
|
|
|
3
30
|
cursor = conn.execute("""
|
|
4
31
|
SELECT sql
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 006
|
|
3
|
+
|
|
4
|
+
Cria índice para acelerar consultas por símbolo e tempo
|
|
5
|
+
utilizando a coluna `time_msc`.
|
|
6
|
+
|
|
7
|
+
Este índice é importante para consultas comuns como:
|
|
8
|
+
|
|
9
|
+
SELECT * FROM ticks
|
|
10
|
+
WHERE symbol = ?
|
|
11
|
+
ORDER BY time_msc
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
SQL = """
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
16
|
+
ON ticks(symbol, time_msc);
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade(conn):
|
|
21
|
+
"""
|
|
22
|
+
Executa a criação do índice `idx_ticks_symbol_time`.
|
|
23
|
+
|
|
24
|
+
A operação é idempotente graças ao uso de
|
|
25
|
+
`CREATE INDEX IF NOT EXISTS`.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
cursor = conn.cursor()
|
|
29
|
+
|
|
30
|
+
cursor.execute(SQL)
|
|
31
|
+
|
|
32
|
+
conn.commit()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration 007
|
|
3
|
+
|
|
4
|
+
Objetivo
|
|
5
|
+
--------
|
|
6
|
+
|
|
7
|
+
1. Remover registros duplicados da tabela `ticks`
|
|
8
|
+
2. Criar índice UNIQUE para evitar duplicação futura
|
|
9
|
+
|
|
10
|
+
Critério de unicidade adotado:
|
|
11
|
+
|
|
12
|
+
(symbol, time_msc)
|
|
13
|
+
|
|
14
|
+
Estratégia
|
|
15
|
+
----------
|
|
16
|
+
|
|
17
|
+
1. Criar tabela temporária com SELECT DISTINCT
|
|
18
|
+
2. Copiar dados únicos
|
|
19
|
+
3. Substituir tabela original
|
|
20
|
+
4. Criar índice UNIQUE
|
|
21
|
+
|
|
22
|
+
Essa estratégia é muito mais rápida do que DELETE em tabelas grandes.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from sqlite3 import Connection
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def upgrade(conn: Connection) -> None:
|
|
29
|
+
cursor = conn.cursor()
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------
|
|
32
|
+
# 1 - criar nova tabela sem duplicados
|
|
33
|
+
# ---------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
cursor.execute(
|
|
36
|
+
"""
|
|
37
|
+
CREATE TABLE IF NOT EXISTS ticks_new AS
|
|
38
|
+
SELECT DISTINCT *
|
|
39
|
+
FROM ticks
|
|
40
|
+
"""
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------
|
|
44
|
+
# 2 - remover tabela antiga
|
|
45
|
+
# ---------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
cursor.execute("DROP TABLE ticks")
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------
|
|
50
|
+
# 3 - renomear tabela nova
|
|
51
|
+
# ---------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
cursor.execute("ALTER TABLE ticks_new RENAME TO ticks")
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------
|
|
56
|
+
# 4 - recriar índice de performance existente
|
|
57
|
+
# ---------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
cursor.execute(
|
|
60
|
+
"""
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
62
|
+
ON ticks(symbol, time_msc)
|
|
63
|
+
"""
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------
|
|
67
|
+
# 5 - criar índice UNIQUE
|
|
68
|
+
# ---------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
cursor.execute(
|
|
71
|
+
"""
|
|
72
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_ticks_unique
|
|
73
|
+
ON ticks(symbol, time_msc)
|
|
74
|
+
"""
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
conn.commit()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def downgrade(conn: Connection) -> None:
|
|
81
|
+
cursor = conn.cursor()
|
|
82
|
+
|
|
83
|
+
# Remove índice UNIQUE caso exista rollback
|
|
84
|
+
|
|
85
|
+
cursor.execute(
|
|
86
|
+
"""
|
|
87
|
+
DROP INDEX IF EXISTS idx_ticks_unique
|
|
88
|
+
"""
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
conn.commit()
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration Runner do mtcli.
|
|
3
|
+
|
|
4
|
+
Responsabilidades:
|
|
5
|
+
|
|
6
|
+
1. Descobrir migrations disponíveis no diretório `mtcli/migrations`
|
|
7
|
+
2. Determinar a versão atual do schema no banco
|
|
8
|
+
3. Executar migrations pendentes em ordem
|
|
9
|
+
4. Registrar migrations aplicadas na tabela `schema_migrations`
|
|
10
|
+
|
|
11
|
+
Formato obrigatório das migrations:
|
|
12
|
+
|
|
13
|
+
NNN_nome_da_migration.py
|
|
14
|
+
|
|
15
|
+
Exemplo:
|
|
16
|
+
|
|
17
|
+
001_initial_schema.py
|
|
18
|
+
002_ticks_time_msc.py
|
|
19
|
+
003_optimize_ticks_without_rowid.py
|
|
20
|
+
|
|
21
|
+
Cada migration deve implementar:
|
|
22
|
+
|
|
23
|
+
def upgrade(conn)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import importlib
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
MIGRATIONS_DIR = Path(__file__).parent
|
|
31
|
+
PACKAGE = "mtcli.migrations"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------
|
|
35
|
+
# Infraestrutura
|
|
36
|
+
# ---------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def ensure_migrations_table(conn):
|
|
39
|
+
"""
|
|
40
|
+
Garante que a tabela `schema_migrations` exista.
|
|
41
|
+
|
|
42
|
+
Esta tabela armazena quais migrations já foram
|
|
43
|
+
aplicadas no banco.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
conn.execute("""
|
|
47
|
+
CREATE TABLE IF NOT EXISTS schema_migrations(
|
|
48
|
+
version INTEGER PRIMARY KEY,
|
|
49
|
+
applied_at TEXT NOT NULL
|
|
50
|
+
)
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
conn.commit()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------
|
|
57
|
+
# Estado do banco
|
|
58
|
+
# ---------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def get_current_version(conn):
|
|
61
|
+
"""
|
|
62
|
+
Retorna a versão atual do schema.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
int
|
|
67
|
+
Maior versão registrada em `schema_migrations`.
|
|
68
|
+
Retorna 0 se nenhuma migration foi aplicada.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
cursor = conn.execute(
|
|
72
|
+
"SELECT MAX(version) FROM schema_migrations"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
row = cursor.fetchone()
|
|
76
|
+
|
|
77
|
+
return row[0] if row and row[0] else 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def mark_version(conn, version):
|
|
81
|
+
"""
|
|
82
|
+
Marca uma migration como aplicada.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
conn.execute(
|
|
86
|
+
"""
|
|
87
|
+
INSERT INTO schema_migrations(version, applied_at)
|
|
88
|
+
VALUES (?, datetime('now'))
|
|
89
|
+
""",
|
|
90
|
+
(version,),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
conn.commit()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------
|
|
97
|
+
# Descoberta de migrations
|
|
98
|
+
# ---------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def discover_migrations():
|
|
101
|
+
"""
|
|
102
|
+
Descobre migrations válidas no diretório.
|
|
103
|
+
|
|
104
|
+
Apenas arquivos no formato:
|
|
105
|
+
|
|
106
|
+
NNN_nome.py
|
|
107
|
+
|
|
108
|
+
serão considerados migrations.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
list[(int,str)]
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
migrations = []
|
|
116
|
+
|
|
117
|
+
for file in MIGRATIONS_DIR.glob("*.py"):
|
|
118
|
+
|
|
119
|
+
if file.name in ("__init__.py", "runner.py"):
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
prefix = file.stem.split("_")[0]
|
|
123
|
+
|
|
124
|
+
# ignora arquivos inválidos
|
|
125
|
+
if not prefix.isdigit():
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
version = int(prefix)
|
|
129
|
+
|
|
130
|
+
module_name = file.stem
|
|
131
|
+
|
|
132
|
+
migrations.append((version, module_name))
|
|
133
|
+
|
|
134
|
+
migrations.sort(key=lambda x: x[0])
|
|
135
|
+
|
|
136
|
+
validate_migrations(migrations)
|
|
137
|
+
|
|
138
|
+
return migrations
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def validate_migrations(migrations):
|
|
142
|
+
"""
|
|
143
|
+
Valida integridade das migrations.
|
|
144
|
+
|
|
145
|
+
Verifica:
|
|
146
|
+
|
|
147
|
+
- versões duplicadas
|
|
148
|
+
- gaps na sequência
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
versions = [v for v, _ in migrations]
|
|
152
|
+
|
|
153
|
+
if len(versions) != len(set(versions)):
|
|
154
|
+
raise RuntimeError("Duplicate migration versions detected.")
|
|
155
|
+
|
|
156
|
+
if not versions:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
expected = list(range(min(versions), max(versions) + 1))
|
|
160
|
+
|
|
161
|
+
if versions != expected:
|
|
162
|
+
raise RuntimeError(
|
|
163
|
+
f"Migration sequence gap detected: {versions}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------
|
|
168
|
+
# Compatibilidade com bancos antigos
|
|
169
|
+
# ---------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
def legacy_database_detected(conn):
|
|
172
|
+
"""
|
|
173
|
+
Detecta bancos antigos sem controle de migrations.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
cursor = conn.execute("""
|
|
177
|
+
SELECT name
|
|
178
|
+
FROM sqlite_master
|
|
179
|
+
WHERE type='table'
|
|
180
|
+
AND name='ticks'
|
|
181
|
+
""")
|
|
182
|
+
|
|
183
|
+
return cursor.fetchone() is not None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def bootstrap_legacy(conn):
|
|
187
|
+
"""
|
|
188
|
+
Inicializa migrations em banco legado.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
if legacy_database_detected(conn):
|
|
192
|
+
|
|
193
|
+
conn.execute("""
|
|
194
|
+
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
|
195
|
+
VALUES (1, datetime('now'))
|
|
196
|
+
""")
|
|
197
|
+
|
|
198
|
+
conn.commit()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------
|
|
202
|
+
# Runner principal
|
|
203
|
+
# ---------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
def run_migrations(conn):
|
|
206
|
+
"""
|
|
207
|
+
Executa migrations pendentes.
|
|
208
|
+
|
|
209
|
+
Fluxo:
|
|
210
|
+
|
|
211
|
+
1. Garante tabela `schema_migrations`
|
|
212
|
+
2. Detecta bancos legados
|
|
213
|
+
3. Descobre migrations
|
|
214
|
+
4. Executa migrations pendentes
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
ensure_migrations_table(conn)
|
|
218
|
+
|
|
219
|
+
bootstrap_legacy(conn)
|
|
220
|
+
|
|
221
|
+
current = get_current_version(conn)
|
|
222
|
+
|
|
223
|
+
migrations = discover_migrations()
|
|
224
|
+
|
|
225
|
+
for version, module_name in migrations:
|
|
226
|
+
|
|
227
|
+
if version <= current:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
module_path = f"{PACKAGE}.{module_name}"
|
|
231
|
+
|
|
232
|
+
module = importlib.import_module(module_path)
|
|
233
|
+
|
|
234
|
+
if not hasattr(module, "upgrade"):
|
|
235
|
+
raise RuntimeError(
|
|
236
|
+
f"Migration {module_name} does not define upgrade()"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
print(f"Applying migration {version}: {module_name}")
|
|
240
|
+
|
|
241
|
+
module.upgrade(conn)
|
|
242
|
+
|
|
243
|
+
mark_version(conn, version)
|
|
244
|
+
|
|
245
|
+
print("Migrations concluídas.")
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mtcli"
|
|
3
|
-
version = "3.8.0.
|
|
3
|
+
version = "3.8.0.dev9"
|
|
4
4
|
description = "Aplicativo CLI para exibir gráficos do MetaTrader 5 screen reader friendly"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Valmir França",email = "vfranca3@gmail.com"}
|
|
@@ -45,6 +45,7 @@ issues = "https://github.com/vfranca/mtcli/issues"
|
|
|
45
45
|
[project.scripts]
|
|
46
46
|
mtcli = "mtcli.cli:mt"
|
|
47
47
|
mt = "mtcli.cli:mt"
|
|
48
|
+
mtdev = "mtcli.cli_dev:cli"
|
|
48
49
|
|
|
49
50
|
[project.entry-points."mtcli.plugins"]
|
|
50
51
|
internals = "mtcli.plugin:register"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
def upgrade(conn):
|
|
2
|
-
|
|
3
|
-
conn.execute("""
|
|
4
|
-
CREATE TABLE IF NOT EXISTS ticks(
|
|
5
|
-
symbol TEXT NOT NULL,
|
|
6
|
-
time INTEGER NOT NULL,
|
|
7
|
-
bid REAL,
|
|
8
|
-
ask REAL,
|
|
9
|
-
last REAL,
|
|
10
|
-
volume REAL,
|
|
11
|
-
flags INTEGER,
|
|
12
|
-
PRIMARY KEY(symbol, time)
|
|
13
|
-
)
|
|
14
|
-
""")
|
|
15
|
-
|
|
16
|
-
conn.execute("""
|
|
17
|
-
CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time
|
|
18
|
-
ON ticks(symbol, time)
|
|
19
|
-
""")
|
|
20
|
-
|
|
21
|
-
conn.commit()
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
def column_exists(conn, table, column):
|
|
2
|
-
|
|
3
|
-
cursor = conn.execute(f"PRAGMA table_info({table})")
|
|
4
|
-
|
|
5
|
-
for row in cursor.fetchall():
|
|
6
|
-
if row[1] == column:
|
|
7
|
-
return True
|
|
8
|
-
|
|
9
|
-
return False
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def upgrade(conn):
|
|
13
|
-
|
|
14
|
-
if not column_exists(conn, "ticks", "time_msc"):
|
|
15
|
-
|
|
16
|
-
conn.execute("""
|
|
17
|
-
ALTER TABLE ticks
|
|
18
|
-
ADD COLUMN time_msc INTEGER
|
|
19
|
-
""")
|
|
20
|
-
|
|
21
|
-
conn.commit()
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import importlib
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
MIGRATIONS_DIR = Path(__file__).parent
|
|
6
|
-
PACKAGE = "mtcli.migrations"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_current_version(conn):
|
|
10
|
-
|
|
11
|
-
cursor = conn.execute(
|
|
12
|
-
"SELECT MAX(version) FROM schema_migrations"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
row = cursor.fetchone()
|
|
16
|
-
|
|
17
|
-
return row[0] if row and row[0] else 0
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def mark_version(conn, version):
|
|
21
|
-
|
|
22
|
-
conn.execute(
|
|
23
|
-
"""
|
|
24
|
-
INSERT INTO schema_migrations(version, applied_at)
|
|
25
|
-
VALUES (?, datetime('now'))
|
|
26
|
-
""",
|
|
27
|
-
(version,),
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
conn.commit()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def discover_migrations():
|
|
34
|
-
|
|
35
|
-
migrations = []
|
|
36
|
-
|
|
37
|
-
for file in MIGRATIONS_DIR.glob("*.py"):
|
|
38
|
-
|
|
39
|
-
if file.name in ("__init__.py", "runner.py"):
|
|
40
|
-
continue
|
|
41
|
-
|
|
42
|
-
version = int(file.name.split("_")[0])
|
|
43
|
-
|
|
44
|
-
module_name = file.stem
|
|
45
|
-
|
|
46
|
-
migrations.append((version, module_name))
|
|
47
|
-
|
|
48
|
-
migrations.sort(key=lambda x: x[0])
|
|
49
|
-
|
|
50
|
-
return migrations
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def legacy_database_detected(conn):
|
|
54
|
-
|
|
55
|
-
cursor = conn.execute("""
|
|
56
|
-
SELECT name
|
|
57
|
-
FROM sqlite_master
|
|
58
|
-
WHERE type='table'
|
|
59
|
-
AND name='ticks'
|
|
60
|
-
""")
|
|
61
|
-
|
|
62
|
-
return cursor.fetchone() is not None
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def bootstrap_legacy(conn):
|
|
66
|
-
|
|
67
|
-
"""
|
|
68
|
-
Marca migration 1 como aplicada se banco já tinha schema.
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
if legacy_database_detected(conn):
|
|
72
|
-
|
|
73
|
-
conn.execute("""
|
|
74
|
-
INSERT OR IGNORE INTO schema_migrations(version, applied_at)
|
|
75
|
-
VALUES (1, datetime('now'))
|
|
76
|
-
""")
|
|
77
|
-
|
|
78
|
-
conn.commit()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def run_migrations(conn):
|
|
82
|
-
|
|
83
|
-
bootstrap_legacy(conn)
|
|
84
|
-
|
|
85
|
-
current = get_current_version(conn)
|
|
86
|
-
|
|
87
|
-
migrations = discover_migrations()
|
|
88
|
-
|
|
89
|
-
for version, module_name in migrations:
|
|
90
|
-
|
|
91
|
-
if version <= current:
|
|
92
|
-
continue
|
|
93
|
-
|
|
94
|
-
module_path = f"{PACKAGE}.{module_name}"
|
|
95
|
-
|
|
96
|
-
module = importlib.import_module(module_path)
|
|
97
|
-
|
|
98
|
-
print(f"Applying migration {version}: {module_name}")
|
|
99
|
-
|
|
100
|
-
module.upgrade(conn)
|
|
101
|
-
|
|
102
|
-
mark_version(conn, version)
|
|
103
|
-
|
|
104
|
-
print("Migrations concluídas.")
|
|
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.dev7/mtcli/plugins/media_movel/tests → mtcli-3.8.0.dev9/mtcli/plugins}/__init__.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
|
{mtcli-3.8.0.dev7 → mtcli-3.8.0.dev9}/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.dev7 → mtcli-3.8.0.dev9}/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.dev7 → mtcli-3.8.0.dev9}/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
|