199 lines
6.8 KiB
Python
199 lines
6.8 KiB
Python
"""
|
|
SQLite to MySQL Migration Script
|
|
|
|
This script migrates data from local SQLite databases (`trading_history.db` and `data/market_data.db`)
|
|
to a remote MySQL database defined in environment variables.
|
|
|
|
Environment Variables required in .env:
|
|
- MYSQL_HOST
|
|
- MYSQL_PORT (default: 3306)
|
|
- MYSQL_USER
|
|
- MYSQL_PASSWORD
|
|
- MYSQL_DB
|
|
|
|
Usage:
|
|
python scripts/migrate_sqlite_to_mysql.py
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import sqlite3
|
|
import pymysql
|
|
from dotenv import load_dotenv
|
|
from tqdm import tqdm
|
|
import pandas as pd
|
|
|
|
# Add project root to sys.path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
load_dotenv()
|
|
|
|
def get_mysql_connection():
|
|
return pymysql.connect(
|
|
host=os.getenv('MYSQL_HOST'),
|
|
port=int(os.getenv('MYSQL_PORT', 3306)),
|
|
user=os.getenv('MYSQL_USER'),
|
|
password=os.getenv('MYSQL_PASSWORD'),
|
|
database=os.getenv('MYSQL_DB'),
|
|
charset='utf8mb4',
|
|
cursorclass=pymysql.cursors.DictCursor
|
|
)
|
|
|
|
def create_mysql_tables(conn):
|
|
with conn.cursor() as cursor:
|
|
# Create trades table
|
|
print("Creating 'trades' table in MySQL...")
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS trades (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
timestamp DATETIME,
|
|
market_title VARCHAR(255),
|
|
market_slug VARCHAR(255),
|
|
model_name VARCHAR(50),
|
|
prediction VARCHAR(10),
|
|
probability DOUBLE,
|
|
action VARCHAR(10),
|
|
price DOUBLE,
|
|
amount DOUBLE,
|
|
size DOUBLE,
|
|
status VARCHAR(20),
|
|
error_message TEXT,
|
|
raw_data JSON,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX idx_market_slug (market_slug),
|
|
INDEX idx_status (status)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
""")
|
|
|
|
# Create ohlcv table
|
|
print("Creating 'ohlcv' table in MySQL...")
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS ohlcv (
|
|
timestamp DATETIME,
|
|
symbol VARCHAR(20),
|
|
timeframe VARCHAR(10),
|
|
open DOUBLE,
|
|
high DOUBLE,
|
|
low DOUBLE,
|
|
close DOUBLE,
|
|
volume DOUBLE,
|
|
PRIMARY KEY (symbol, timeframe, timestamp),
|
|
INDEX idx_time (timestamp)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
""")
|
|
conn.commit()
|
|
|
|
def migrate_trades(mysql_conn):
|
|
sqlite_db_path = "trading_history.db"
|
|
if not os.path.exists(sqlite_db_path):
|
|
print(f"Skipping trades migration: {sqlite_db_path} not found.")
|
|
return
|
|
|
|
print(f"Migrating trades from {sqlite_db_path}...")
|
|
|
|
with sqlite3.connect(sqlite_db_path) as sqlite_conn:
|
|
sqlite_cursor = sqlite_conn.cursor()
|
|
|
|
# Check if table exists
|
|
sqlite_cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='trades';")
|
|
if not sqlite_cursor.fetchone():
|
|
print("Table 'trades' not found in SQLite.")
|
|
return
|
|
|
|
sqlite_cursor.execute("SELECT * FROM trades")
|
|
rows = sqlite_cursor.fetchall()
|
|
columns = [description[0] for description in sqlite_cursor.description]
|
|
|
|
if not rows:
|
|
print("No trades data to migrate.")
|
|
return
|
|
|
|
print(f"Found {len(rows)} trade records.")
|
|
|
|
# Insert into MySQL
|
|
with mysql_conn.cursor() as mysql_cursor:
|
|
# Prepare SQL statement
|
|
# Map columns. SQLite 'timestamp' is text, MySQL needs valid datetime format or string.
|
|
# We skip 'id' to let MySQL handle auto-increment or we can preserve it.
|
|
# Let's preserve ID if possible, but usually better to let destination handle it unless we need consistency.
|
|
# User wants migration, preserving ID is safer for history.
|
|
|
|
placeholders = ', '.join(['%s'] * len(columns))
|
|
cols_str = ', '.join(columns)
|
|
|
|
sql = f"INSERT IGNORE INTO trades ({cols_str}) VALUES ({placeholders})"
|
|
|
|
mysql_cursor.executemany(sql, rows)
|
|
mysql_conn.commit()
|
|
print(f"Migrated {mysql_cursor.rowcount} trade records.")
|
|
|
|
def migrate_ohlcv(mysql_conn):
|
|
sqlite_db_path = "data/market_data.db"
|
|
if not os.path.exists(sqlite_db_path):
|
|
print(f"Skipping OHLCV migration: {sqlite_db_path} not found.")
|
|
return
|
|
|
|
print(f"Migrating OHLCV from {sqlite_db_path}...")
|
|
|
|
with sqlite3.connect(sqlite_db_path) as sqlite_conn:
|
|
# Use pandas for easier chunk reading
|
|
try:
|
|
# Check total count
|
|
total_rows = pd.read_sql_query("SELECT COUNT(*) FROM ohlcv", sqlite_conn).iloc[0, 0]
|
|
print(f"Found {total_rows} OHLCV records.")
|
|
|
|
chunk_size = 10000
|
|
offset = 0
|
|
|
|
with tqdm(total=total_rows, desc="Migrating OHLCV") as pbar:
|
|
while True:
|
|
df = pd.read_sql_query(f"SELECT * FROM ohlcv LIMIT {chunk_size} OFFSET {offset}", sqlite_conn)
|
|
if df.empty:
|
|
break
|
|
|
|
# Convert DataFrame to list of tuples
|
|
data = [tuple(x) for x in df.to_numpy()]
|
|
|
|
with mysql_conn.cursor() as mysql_cursor:
|
|
sql = """
|
|
INSERT IGNORE INTO ohlcv
|
|
(timestamp, symbol, timeframe, open, high, low, close, volume)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
"""
|
|
# Select columns explicitly to match SQL
|
|
cols = ['timestamp', 'symbol', 'timeframe', 'open', 'high', 'low', 'close', 'volume']
|
|
batch_data = [tuple(row[col] for col in cols) for _, row in df.iterrows()]
|
|
|
|
mysql_cursor.executemany(sql, batch_data)
|
|
mysql_conn.commit()
|
|
|
|
offset += chunk_size
|
|
pbar.update(len(df))
|
|
|
|
except Exception as e:
|
|
print(f"Error reading/migrating OHLCV: {e}")
|
|
|
|
def main():
|
|
if not os.getenv('MYSQL_HOST'):
|
|
print("Error: MYSQL_HOST not set in .env")
|
|
return
|
|
|
|
try:
|
|
print("Connecting to MySQL...")
|
|
mysql_conn = get_mysql_connection()
|
|
print("Connected.")
|
|
|
|
create_mysql_tables(mysql_conn)
|
|
|
|
migrate_trades(mysql_conn)
|
|
migrate_ohlcv(mysql_conn)
|
|
|
|
mysql_conn.close()
|
|
print("\nMigration completed successfully.")
|
|
|
|
except Exception as e:
|
|
print(f"\nMigration failed: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|