fix: include exchange_code in latest BUY matching key (#323)
This commit is contained in:
53
src/db.py
53
src/db.py
@@ -290,22 +290,47 @@ def _resolve_session_id(*, market: str, session_id: str | None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_latest_buy_trade(
|
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:
|
) -> dict[str, Any] | None:
|
||||||
"""Fetch the most recent BUY trade for a stock and market."""
|
"""Fetch the most recent BUY trade for a stock and market."""
|
||||||
cursor = conn.execute(
|
if exchange_code:
|
||||||
"""
|
cursor = conn.execute(
|
||||||
SELECT decision_id, price, quantity
|
"""
|
||||||
FROM trades
|
SELECT decision_id, price, quantity
|
||||||
WHERE stock_code = ?
|
FROM trades
|
||||||
AND market = ?
|
WHERE stock_code = ?
|
||||||
AND action = 'BUY'
|
AND market = ?
|
||||||
AND decision_id IS NOT NULL
|
AND action = 'BUY'
|
||||||
ORDER BY timestamp DESC
|
AND decision_id IS NOT NULL
|
||||||
LIMIT 1
|
AND (
|
||||||
""",
|
exchange_code = ?
|
||||||
(stock_code, market),
|
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()
|
row = cursor.fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
|
|||||||
14
src/main.py
14
src/main.py
@@ -1655,7 +1655,12 @@ async def trading_cycle(
|
|||||||
logger.warning("Telegram notification failed: %s", exc)
|
logger.warning("Telegram notification failed: %s", exc)
|
||||||
|
|
||||||
if decision.action == "SELL" and order_succeeded:
|
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:
|
if buy_trade and buy_trade.get("price") is not None:
|
||||||
buy_price = float(buy_trade["price"])
|
buy_price = float(buy_trade["price"])
|
||||||
buy_qty = int(buy_trade.get("quantity") or 1)
|
buy_qty = int(buy_trade.get("quantity") or 1)
|
||||||
@@ -2752,7 +2757,12 @@ async def run_daily_session(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if decision.action == "SELL" and order_succeeded:
|
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:
|
if buy_trade and buy_trade.get("price") is not None:
|
||||||
buy_price = float(buy_trade["price"])
|
buy_price = float(buy_trade["price"])
|
||||||
buy_qty = int(buy_trade.get("quantity") or 1)
|
buy_qty = int(buy_trade.get("quantity") or 1)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import tempfile
|
import tempfile
|
||||||
import os
|
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:
|
def test_get_open_position_returns_latest_buy() -> None:
|
||||||
@@ -329,3 +329,39 @@ def test_log_trade_unknown_market_falls_back_to_unknown_session() -> None:
|
|||||||
row = conn.execute("SELECT session_id FROM trades ORDER BY id DESC LIMIT 1").fetchone()
|
row = conn.execute("SELECT session_id FROM trades ORDER BY id DESC LIMIT 1").fetchone()
|
||||||
assert row is not None
|
assert row is not None
|
||||||
assert row[0] == "UNKNOWN"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user