Migrate data pipeline from SQLite to PostgreSQL + Docker setup
- Add db.py PostgreSQL abstraction layer (connection, upsert, table mgmt) - Replace sqlite3 with psycopg2 in: live_candle_fetcher, resampler, data_fetcher, fetch_history, import_csv, indicators, base_strategy - Sanitize table names (colons -> underscores) for PostgreSQL compat - Replace INSERT OR REPLACE with ON CONFLICT upserts - Replace pandas to_sql() with batch upsert_candles() - Add scripts: resampler_loop, gap_detector, backup_runner, cron_scheduler - Add migrate_sqlite_to_pg.py for one-time data migration - Add Dockerfile, docker-compose.yml, supervisord.conf - Add postgres/postgresql.conf tuned for 4GB RAM (Synology DS1513+) - Add .dockerignore, .env.docker.example, secrets template - Update requirements.txt (psycopg2-binary), .gitignore - Add MIGRATION_PLAN.md with full plan and todo list
This commit is contained in:
105
migrate_sqlite_to_pg.py
Normal file
105
migrate_sqlite_to_pg.py
Normal file
@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SQLite to PostgreSQL Migration Script
|
||||
|
||||
Reads candle data from a SQLite database and writes it to PostgreSQL.
|
||||
This is a one-time migration tool used to transfer existing historical
|
||||
data from the old SQLite database to the new PostgreSQL database.
|
||||
|
||||
Usage:
|
||||
python migrate_sqlite_to_pg.py --sqlite-path _data/market_data.db --log-level normal
|
||||
|
||||
The script:
|
||||
1. Connects to both SQLite (source) and PostgreSQL (destination)
|
||||
2. Enumerates all candle tables (skipping legacy tables like market_cap)
|
||||
3. For each table, reads data from SQLite and upserts to PostgreSQL
|
||||
4. Handles table name sanitization (colons → underscores)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from logging_utils import setup_logging
|
||||
from db import get_connection, sanitize_table_name, upsert_candles, create_candle_table
|
||||
|
||||
TIMEFRAMES = [
|
||||
'1m', '3m', '5m', '15m', '30m', '37m', '148m',
|
||||
'1h', '2h', '4h', '8h', '12h', '1d', '3d', '1w', '1month'
|
||||
]
|
||||
|
||||
|
||||
def get_candle_tables(sqlite_conn):
|
||||
"""Get all candle table names from SQLite (excluding legacy tables)."""
|
||||
cursor = sqlite_conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||
all_tables = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
candle_tables = []
|
||||
for table in all_tables:
|
||||
for tf in TIMEFRAMES:
|
||||
if table.endswith(f'_{tf}'):
|
||||
candle_tables.append(table)
|
||||
break
|
||||
|
||||
return candle_tables
|
||||
|
||||
|
||||
def parse_table_name(table_name):
|
||||
"""Parse a table name into (coin, timeframe)."""
|
||||
for tf in TIMEFRAMES:
|
||||
suffix = f'_{tf}'
|
||||
if table_name.endswith(suffix):
|
||||
coin = table_name[:-len(suffix)]
|
||||
return coin, tf
|
||||
return table_name, '1m'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Migrate data from SQLite to PostgreSQL.")
|
||||
parser.add_argument("--sqlite-path", default="_data/market_data.db",
|
||||
help="Path to the SQLite database file.")
|
||||
parser.add_argument("--log-level", default="normal", choices=['off', 'normal', 'debug'])
|
||||
args = parser.parse_args()
|
||||
|
||||
setup_logging(args.log_level, 'Migrator')
|
||||
|
||||
if not os.path.exists(args.sqlite_path):
|
||||
logging.error(f"SQLite database not found at '{args.sqlite_path}'")
|
||||
sys.exit(1)
|
||||
|
||||
sqlite_conn = sqlite3.connect(args.sqlite_path)
|
||||
pg_conn = get_connection()
|
||||
|
||||
tables = get_candle_tables(sqlite_conn)
|
||||
logging.info(f"Found {len(tables)} candle tables to migrate")
|
||||
|
||||
for table_name in tables:
|
||||
coin, timeframe = parse_table_name(table_name)
|
||||
pg_table = sanitize_table_name(coin, timeframe)
|
||||
|
||||
logging.info(f"Migrating {table_name} -> {pg_table}")
|
||||
|
||||
df = pd.read_sql(f'SELECT * FROM "{table_name}"', sqlite_conn)
|
||||
if df.empty:
|
||||
logging.warning(f"Table {table_name} is empty, skipping")
|
||||
continue
|
||||
|
||||
create_candle_table(pg_conn, pg_table)
|
||||
|
||||
records = list(df.itertuples(index=False, name=None))
|
||||
upsert_candles(pg_conn, pg_table, records)
|
||||
|
||||
logging.info(f"Migrated {len(records)} rows to {pg_table}")
|
||||
|
||||
sqlite_conn.close()
|
||||
pg_conn.close()
|
||||
logging.info("Migration complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user