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 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-06-16 00:15:19 -04:00
1 parent c4669fe0ce
commit b1ef0930ca
5 files changed
+32 -5

No files matched your search

+11 -1
View File
@@ -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
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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
+18 -1
View File
@@ -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 "
+1 -1
View File
@@ -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 (?, ?, ?, ?, ?, ?, ?, ?, ?)