From b1ef0930ca8fdf6fff8fdb671da18ad84bbcf2c6 Mon Sep 17 00:00:00 2001 From: momentum dev Date: Tue, 16 Jun 2026 00:15:19 -0400 Subject: [PATCH] WS0 cleanup: DuckDB-free + wire DATABASE_URL - Drop duckdb dependency (pyproject, requirements) -> psycopg2-binary. - run_live_paper: boot log shows the real Postgres target (masked); add _db_target_desc() helper. - .env.example: document DATABASE_URL / PG* vars; mark WATCHER_EVENT_DB_PATH legacy. - evaluation/storage.py: INSERT OR REPLACE -> INSERT (setup_id is a fresh uuid). Verified: 67/67 tests green on Postgres with duckdb UNINSTALLED; app boots against the live endpoint (127.0.0.1:5432 proxy) and wrote 205 daily bars. Co-Authored-By: Claude Opus 4.8 --- .env.example | 12 +++++++++++- pyproject.toml | 2 +- requirements.txt | 2 +- run_live_paper.py | 19 ++++++++++++++++++- strategy/evaluation/storage.py | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index dca0fe5..fe3c073 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,16 @@ # Momentum — environment configuration (copy to .env and fill) # ============================================================ +# ---- Database (PostgreSQL) — the single datastore (event store + bar warehouse) +# Either a full URL: +DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/momentum +# ...or individual vars (used only if DATABASE_URL is unset): +# PGHOST=127.0.0.1 +# PGPORT=5432 +# PGUSER=admin +# PGPASSWORD= +# PGDATABASE=momentum + # ---- Alpaca (market data + paper broker) — REQUIRED for live paper trading ALPACA_API_KEY= ALPACA_SECRET_KEY= @@ -47,7 +57,7 @@ TRADING_MAX_DAILY_LOSS_PCT=0.03 # daily-loss circuit breaker # Intraday watcher (signal engine) WATCHER_ENABLED=1 WATCHER_INTERVAL_SECONDS=60 -WATCHER_EVENT_DB_PATH=./data/momentum.duckdb +# WATCHER_EVENT_DB_PATH is legacy and ignored — the datastore is PostgreSQL (see DATABASE_URL above) WATCHER_MAX_SYMBOLS=25 WATCHER_MIN_BARS=30 WATCHER_MIN_QUALITY=0.30 diff --git a/pyproject.toml b/pyproject.toml index 8811e26..ddf85b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.4.0" description = "Small-cap momentum research & paper-trading system" requires-python = ">=3.11" dependencies = [ - "duckdb>=1.0.0", + "psycopg2-binary>=2.9.0", "pandas>=2.0.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", diff --git a/requirements.txt b/requirements.txt index 659810a..6f2e622 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -duckdb>=1.0.0 +psycopg2-binary>=2.9.0 pandas>=2.0.0 pydantic>=2.0.0 pytest>=7.0.0 diff --git a/run_live_paper.py b/run_live_paper.py index a3d15b2..b7785d8 100644 --- a/run_live_paper.py +++ b/run_live_paper.py @@ -65,6 +65,23 @@ def _now_session_date(): return session_date +def _db_target_desc() -> str: + """Human-readable Postgres target for the boot log (password masked).""" + url = os.environ.get("DATABASE_URL") + if url: + if "://" in url and "@" in url: + scheme, rest = url.split("://", 1) + creds, host = rest.split("@", 1) + user = creds.split(":", 1)[0] + return f"{scheme}://{user}:***@{host}" + return url + host = os.environ.get("PGHOST", "localhost") + port = os.environ.get("PGPORT", "5432") + db = os.environ.get("PGDATABASE", "momentum") + user = os.environ.get("PGUSER", "") + return f"postgresql://{user}@{host}:{port}/{db}" + + def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Momentum live paper-trading loop") parser.add_argument("--once", action="store_true", help="run one pass and exit") @@ -188,7 +205,7 @@ def main(argv: list[str] | None = None) -> int: symbols: list[str] = rt["symbols"] client = rt["client"] - print(f"[boot] session={rt['session_id']} event_db={rt['event_db']}") + print(f"[boot] session={rt['session_id']} db={_db_target_desc()}") print(f"[boot] watching: {', '.join(symbols)}") if not rt["has_keys"]: print("[boot] no ALPACA_API_KEY/SECRET — dry mode: ingestion, sync and " diff --git a/strategy/evaluation/storage.py b/strategy/evaluation/storage.py index 16f258a..bd98314 100644 --- a/strategy/evaluation/storage.py +++ b/strategy/evaluation/storage.py @@ -13,7 +13,7 @@ def store_setup_event(con, symbol: str, session_date, result: dict) -> str: best = setups[0] if setups else {} con.execute( """ - INSERT OR REPLACE INTO setup_events + INSERT INTO setup_events (setup_id, symbol, setup_time, session_date, setup_name, entry_reference_price, invalidation_price, gap_pct, relative_volume) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)