feat: add trade_id + full trade record to trade_results
Each buy generates a UUID trade_id stored in positions table. Each sell links via same trade_id in trade_results, enabling round-trip grouping of buy→sell pairs. Additional fields saved per trade: - fee_krw: commission amount (0.05% each side) - krw_profit: net KRW profit/loss after fees - buy_price / sell_price: exact prices - invested_krw: capital deployed - sell_reason: trailing stop / time stop / etc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,7 @@ def upsert_position(
|
||||
amount: float,
|
||||
invested_krw: int,
|
||||
entry_time: str, # ISO 포맷 문자열
|
||||
trade_id: str = "",
|
||||
) -> None:
|
||||
"""포지션 저장 또는 갱신 (MERGE)."""
|
||||
sql = """
|
||||
@@ -124,9 +125,9 @@ def upsert_position(
|
||||
invested_krw = :invested_krw,
|
||||
updated_at = SYSTIMESTAMP
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (ticker, buy_price, peak_price, amount, invested_krw, entry_time)
|
||||
INSERT (ticker, buy_price, peak_price, amount, invested_krw, entry_time, trade_id)
|
||||
VALUES (:ticker, :buy_price, :peak_price, :amount, :invested_krw,
|
||||
TO_TIMESTAMP(:entry_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6'))
|
||||
TO_TIMESTAMP(:entry_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6'), :trade_id)
|
||||
"""
|
||||
with _conn() as conn:
|
||||
conn.cursor().execute(sql, {
|
||||
@@ -136,6 +137,7 @@ def upsert_position(
|
||||
"amount": amount,
|
||||
"invested_krw": invested_krw,
|
||||
"entry_time": entry_time,
|
||||
"trade_id": trade_id,
|
||||
})
|
||||
|
||||
|
||||
@@ -170,12 +172,37 @@ def ensure_trade_results_table() -> None:
|
||||
raise
|
||||
|
||||
|
||||
def record_trade(ticker: str, is_win: bool, pnl_pct: float) -> None:
|
||||
"""거래 결과 저장."""
|
||||
def record_trade(
|
||||
ticker: str,
|
||||
is_win: bool,
|
||||
pnl_pct: float,
|
||||
fee_krw: float = 0.0,
|
||||
krw_profit: float = 0.0,
|
||||
trade_id: str = "",
|
||||
buy_price: float = 0.0,
|
||||
sell_price: float = 0.0,
|
||||
invested_krw: int = 0,
|
||||
sell_reason: str = "",
|
||||
) -> None:
|
||||
"""거래 결과 저장 (수수료·KRW 손익·trade_id 포함)."""
|
||||
with _conn() as conn:
|
||||
conn.cursor().execute(
|
||||
"INSERT INTO trade_results (ticker, is_win, pnl_pct) VALUES (:t, :w, :p)",
|
||||
{"t": ticker, "w": 1 if is_win else 0, "p": round(pnl_pct, 4)},
|
||||
"INSERT INTO trade_results "
|
||||
"(ticker, is_win, pnl_pct, fee_krw, krw_profit, "
|
||||
" trade_id, buy_price, sell_price, invested_krw, sell_reason) "
|
||||
"VALUES (:t, :w, :p, :f, :k, :tid, :bp, :sp, :ikrw, :sr)",
|
||||
{
|
||||
"t": ticker,
|
||||
"w": 1 if is_win else 0,
|
||||
"p": round(pnl_pct, 4),
|
||||
"f": round(fee_krw, 2),
|
||||
"k": round(krw_profit, 2),
|
||||
"tid": trade_id,
|
||||
"bp": buy_price,
|
||||
"sp": sell_price,
|
||||
"ikrw": invested_krw,
|
||||
"sr": sell_reason,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -250,7 +277,8 @@ def load_positions() -> list[dict]:
|
||||
"""저장된 전체 포지션 로드."""
|
||||
sql = """
|
||||
SELECT ticker, buy_price, peak_price, amount, invested_krw,
|
||||
TO_CHAR(entry_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6') AS entry_time
|
||||
TO_CHAR(entry_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF6') AS entry_time,
|
||||
trade_id
|
||||
FROM positions
|
||||
"""
|
||||
with _conn() as conn:
|
||||
@@ -265,6 +293,7 @@ def load_positions() -> list[dict]:
|
||||
"amount": float(r[3]),
|
||||
"invested_krw": int(r[4]),
|
||||
"entry_time": r[5],
|
||||
"trade_id": r[6] or "",
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user