feat(scan): legacy position scan — re-key, Manifold re-validate, auto-close
CI/CD / build-and-push (push) Successful in 2m21s

Adds run_legacy_scan() that executes once at startup before the trading loop:

  1. Re-keys every open DB position using the current market_family_key()
  2. Groups by new family key; KEEP = highest edge_net, CLOSE_RECOMMENDED = sibling
  3. Manifold re-query for positions whose family key changed; if corrected
     probability contradicts the trade direction → CLOSE_RECOMMENDED
  4. Logs full report (KEEP / REVIEW / CLOSE_RECOMMENDED) before any closures
  5. In paper mode: auto-closes all CLOSE_RECOMMENDED positions

For the existing Ohio bug:
  - Democrats win Ohio governor (629557): CLOSE_RECOMMENDED
    family changed ohio-democrat-2026 → ohio-gubernatorial-2026
    Manifold re-query confirms prob=0.05 contradicts BUY_YES (inversion bug)
    $X returned to cash at break-even
  - Republicans win Ohio governor (629558): KEEP
    higher edge_net (0.349 > 0.247)

Infrastructure:
  - schema.sql: closed_at TIMESTAMPTZ, close_reason TEXT on trades
  - db.py: all open-position queries filter WHERE closed_at IS NULL
           + close_paper_position(market_id, reason)
  - paper.py: close_legacy_position(market_id, reason) → float

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-04-17 10:43:45 +00:00
parent 9add52ab05
commit d698544f30
4 changed files with 183 additions and 32 deletions
+15 -9
View File
@@ -65,15 +65,11 @@ class Database:
)
async def get_open_positions(self) -> dict[str, float]:
"""Return {market_id: total_net_cost} for all trades in DB.
Since there is no closed flag, every trade in the DB is treated as an
open position. After a TRUNCATE the query returns nothing, so the
portfolio correctly resets to a full bankroll.
"""
"""Return {market_id: total_net_cost} for all open (not closed) trades in DB."""
async with self._pool.acquire() as conn:
rows = await conn.fetch(
"SELECT market_id, SUM(net_cost) AS total FROM trades GROUP BY market_id"
"SELECT market_id, SUM(net_cost) AS total "
"FROM trades WHERE closed_at IS NULL GROUP BY market_id"
)
return {r["market_id"]: float(r["total"]) for r in rows}
@@ -85,7 +81,8 @@ class Database:
"""
async with self._pool.acquire() as conn:
rows = await conn.fetch(
"SELECT DISTINCT family_key FROM trades WHERE family_key IS NOT NULL"
"SELECT DISTINCT family_key FROM trades "
"WHERE family_key IS NOT NULL AND closed_at IS NULL"
)
return {r["family_key"] for r in rows if r["family_key"]}
@@ -101,11 +98,20 @@ class Database:
SELECT DISTINCT ON (market_id)
market_id, question, direction, edge_net, family_key, timestamp
FROM trades
WHERE paper = TRUE
WHERE paper = TRUE AND closed_at IS NULL
ORDER BY market_id, timestamp DESC
""")
return [dict(r) for r in rows]
async def close_paper_position(self, market_id: str, reason: str = "") -> None:
"""Mark a paper position as closed (sets closed_at timestamp)."""
async with self._pool.acquire() as conn:
await conn.execute(
"UPDATE trades SET closed_at = NOW(), close_reason = $2 "
"WHERE market_id = $1 AND closed_at IS NULL",
market_id, reason,
)
async def get_recent_trades(self, limit: int = 100) -> list[dict]:
async with self._pool.acquire() as conn:
rows = await conn.fetch(
+10
View File
@@ -98,3 +98,13 @@ ALTER TABLE signals ADD COLUMN IF NOT EXISTS passed_net BOOLEAN;
CREATE INDEX IF NOT EXISTS idx_signals_market ON signals(market_id);
CREATE INDEX IF NOT EXISTS idx_trades_family ON trades(family_key);
-- ─────────────────────────────────────────────────────────────────────────────
-- Position lifecycle: legacy scan can close erroneous paper positions.
-- closed_at IS NULL → position is open (all open-position queries filter this).
-- closed_at NOT NULL → position closed; close_reason explains why.
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE trades ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ;
ALTER TABLE trades ADD COLUMN IF NOT EXISTS close_reason TEXT;
CREATE INDEX IF NOT EXISTS idx_trades_closed ON trades(closed_at) WHERE closed_at IS NOT NULL;