From 92261da414684e28099940583a0b7e8037426247 Mon Sep 17 00:00:00 2001 From: agentson Date: Sat, 28 Feb 2026 14:38:53 +0900 Subject: [PATCH] fix: include exchange_code in latest BUY matching key (#323) --- src/db.py | 53 +++++++++++++++++++++++++++++++++++------------- src/main.py | 14 +++++++++++-- tests/test_db.py | 38 +++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/db.py b/src/db.py index 0839521..e161de3 100644 --- a/src/db.py +++ b/src/db.py @@ -312,22 +312,47 @@ def _resolve_session_id(*, market: str, session_id: str | None) -> str: def get_latest_buy_trade( - conn: sqlite3.Connection, stock_code: str, market: str + conn: sqlite3.Connection, + stock_code: str, + market: str, + exchange_code: str | None = None, ) -> dict[str, Any] | None: """Fetch the most recent BUY trade for a stock and market.""" - cursor = conn.execute( - """ - SELECT decision_id, price, quantity - FROM trades - WHERE stock_code = ? - AND market = ? - AND action = 'BUY' - AND decision_id IS NOT NULL - ORDER BY timestamp DESC - LIMIT 1 - """, - (stock_code, market), - ) + if exchange_code: + cursor = conn.execute( + """ + SELECT decision_id, price, quantity + FROM trades + WHERE stock_code = ? + AND market = ? + AND action = 'BUY' + AND decision_id IS NOT NULL + AND ( + exchange_code = ? + OR exchange_code IS NULL + OR exchange_code = '' + ) + ORDER BY + CASE WHEN exchange_code = ? THEN 0 ELSE 1 END, + timestamp DESC + LIMIT 1 + """, + (stock_code, market, exchange_code, exchange_code), + ) + else: + cursor = conn.execute( + """ + SELECT decision_id, price, quantity + FROM trades + WHERE stock_code = ? + AND market = ? + AND action = 'BUY' + AND decision_id IS NOT NULL + ORDER BY timestamp DESC + LIMIT 1 + """, + (stock_code, market), + ) row = cursor.fetchone() if not row: return None diff --git a/src/main.py b/src/main.py index 97f9fd8..4bb7b60 100644 --- a/src/main.py +++ b/src/main.py @@ -1659,7 +1659,12 @@ async def trading_cycle( logger.warning("Telegram notification failed: %s", exc) if decision.action == "SELL" and order_succeeded: - buy_trade = get_latest_buy_trade(db_conn, stock_code, market.code) + buy_trade = get_latest_buy_trade( + db_conn, + stock_code, + market.code, + exchange_code=market.exchange_code, + ) if buy_trade and buy_trade.get("price") is not None: buy_price = float(buy_trade["price"]) buy_qty = int(buy_trade.get("quantity") or 1) @@ -2759,7 +2764,12 @@ async def run_daily_session( continue if decision.action == "SELL" and order_succeeded: - buy_trade = get_latest_buy_trade(db_conn, stock_code, market.code) + buy_trade = get_latest_buy_trade( + db_conn, + stock_code, + market.code, + exchange_code=market.exchange_code, + ) if buy_trade and buy_trade.get("price") is not None: buy_price = float(buy_trade["price"]) buy_qty = int(buy_trade.get("quantity") or 1) diff --git a/tests/test_db.py b/tests/test_db.py index 9bd190d..fb2feb9 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -3,7 +3,7 @@ import tempfile import os -from src.db import get_open_position, init_db, log_trade +from src.db import get_latest_buy_trade, get_open_position, init_db, log_trade def test_get_open_position_returns_latest_buy() -> None: @@ -331,6 +331,42 @@ def test_log_trade_unknown_market_falls_back_to_unknown_session() -> None: assert row[0] == "UNKNOWN" +def test_get_latest_buy_trade_prefers_exchange_code_match() -> None: + conn = init_db(":memory:") + log_trade( + conn=conn, + stock_code="AAPL", + action="BUY", + confidence=80, + rationale="legacy", + quantity=10, + price=120.0, + market="US_NASDAQ", + exchange_code="", + decision_id="legacy-buy", + ) + log_trade( + conn=conn, + stock_code="AAPL", + action="BUY", + confidence=85, + rationale="matched", + quantity=5, + price=125.0, + market="US_NASDAQ", + exchange_code="NASD", + decision_id="matched-buy", + ) + matched = get_latest_buy_trade( + conn, + stock_code="AAPL", + market="US_NASDAQ", + exchange_code="NASD", + ) + assert matched is not None + assert matched["decision_id"] == "matched-buy" + + def test_decision_logs_session_id_migration_backfills_unknown() -> None: import sqlite3 -- 2.49.1