Files
hyper/db.py
DiTus 1a95fe1caa Clean up unused files, organize structure, update docs
- Delete obsolete files: data_fetcher_old.py, market_old.py, base_strategy.py (root),
  strategy_sma_cross.py, and old architecture remnants (address_monitor.py,
  position_monitor.py, trade_log.py, wallet_data.py, whale_tracker.py)
- Delete zero-byte Docker artifacts and runtime files (clp_hedger.log,
  clp_hedger/hedge_status.json)
- Move one-off utility scripts to scripts/ directory
- Move example/template files to .temp/ directory
- Update .gitignore: add entries for clp_hedger.log, clp_hedger/hedge_status.json,
  Docker layer hash files, Using, Running, and backups/
- Update .dockerignore: add clp_hedger.log, clp_hedger/hedge_status.json, backups/
- Create example config files: _data/strategies.json.example,
  _data/backtesting_conf.json.example, _data/coin_precision.json.example
- Update GEMINI.md: remove outdated session summaries and duplicate review section
- Update review.md: add cleanup status section, update remaining recommendations
- Update MIGRATION_PLAN.md: mark completed phases, update file references
- Update DOCKER_MIGRATION_GUIDE.md: update import_csv.py path reference
2026-08-05 09:50:36 +02:00

135 lines
4.1 KiB
Python

"""
PostgreSQL database abstraction layer for the Hyperliquid trading toolkit.
Provides a thin wrapper around psycopg2 to centralize database operations,
handle table name sanitization, and abstract SQL dialect differences
from the SQLite-based codebase.
"""
import os
import psycopg2
from psycopg2.extras import execute_values
PG_CONN_STR = os.environ.get(
"PG_CONN_STR",
"postgresql://hyper:hyper@localhost:5432/hyper"
)
def get_connection():
"""Return a new psycopg2 connection to the PostgreSQL database."""
return psycopg2.connect(PG_CONN_STR)
def sanitize_table_name(coin, timeframe):
"""
Sanitize a coin/timeframe pair into a PostgreSQL-safe table name.
Replaces colons with underscores (e.g., 'xyz:BRENTOIL' -> 'xyz_BRENTOIL')
to ensure compatibility with PostgreSQL identifier rules.
"""
return f"{coin.replace(':', '_')}_{timeframe}"
def create_candle_table(conn, table_name):
"""
Create a candle table if it does not already exist.
Schema matches the original SQLite layout:
datetime_utc, timestamp_ms (PK), open, high, low, close, volume, number_of_trades
Also creates an index on datetime_utc for time-range queries.
"""
with conn.cursor() as cur:
cur.execute(f'''
CREATE TABLE IF NOT EXISTS "{table_name}" (
datetime_utc TIMESTAMP,
timestamp_ms BIGINT PRIMARY KEY,
open REAL,
high REAL,
low REAL,
close REAL,
volume REAL,
number_of_trades INTEGER
)
''')
cur.execute(
f'CREATE INDEX IF NOT EXISTS "idx_{table_name}_time" ON "{table_name}"(datetime_utc)'
)
conn.commit()
def upsert_candles(conn, table_name, records):
"""
Batch upsert candle records using PostgreSQL ON CONFLICT.
Args:
conn: psycopg2 connection
table_name: sanitized table name (e.g., 'BTC_1m')
records: list of tuples (datetime_utc, timestamp_ms, open, high,
low, close, volume, number_of_trades)
Returns:
Number of records upserted.
"""
if not records:
return 0
records = list({r[1]: r for r in records}.values())
with conn.cursor() as cur:
execute_values(
cur,
f'''
INSERT INTO "{table_name}"
(datetime_utc, timestamp_ms, open, high, low, close, volume, number_of_trades)
VALUES %s
ON CONFLICT (timestamp_ms) DO UPDATE SET
datetime_utc = EXCLUDED.datetime_utc,
open = EXCLUDED.open,
high = EXCLUDED.high,
low = EXCLUDED.low,
close = EXCLUDED.close,
volume = EXCLUDED.volume,
number_of_trades = EXCLUDED.number_of_trades
''',
records,
page_size=1000
)
conn.commit()
return len(records)
def get_last_timestamp(conn, table_name):
"""Return the most recent timestamp_ms from a table, or None."""
with conn.cursor() as cur:
cur.execute(f'SELECT MAX(timestamp_ms) FROM "{table_name}"')
result = cur.fetchone()
return result[0] if result and result[0] is not None else None
def get_table_count(conn, table_name):
"""Return the total row count of a table."""
with conn.cursor() as cur:
cur.execute(f'SELECT COUNT(*) FROM "{table_name}"')
return cur.fetchone()[0]
def table_exists(conn, table_name):
"""Check if a table exists in the database."""
with conn.cursor() as cur:
cur.execute(
"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = %s)",
(table_name,)
)
return cur.fetchone()[0]
def get_table_columns(conn, table_name):
"""Return a list of column names for a table."""
with conn.cursor() as cur:
cur.execute(
"SELECT column_name FROM information_schema.columns WHERE table_name = %s",
(table_name,)
)
return [row[0] for row in cur.fetchall()]