"""매매 비용·순손익 순수 계산 모듈. settings·DB·네트워크를 참조하지 않는다(indicators_core / sizing_core 와 같은 원칙). **비용률 숫자는 이 모듈에 없다** — 호출부가 settings(KR_COMMISSION_RATE, KR_SELL_TAX_RATE_KOSPI/KOSDAQ)에서 꺼내 넘긴다. 비용 정의는 스윙 집행 경로(`trade_helpers.calculate_trade_costs` → `trade_executor` 매도 기록)와 동일하다: - 매수 수수료 = 진입가 × 수량 × 수수료율 - 매도 수수료 = 청산가 × 수량 × 수수료율 - 매도 거래세 = 청산가 × 수량 × 거래세율 - 각 항목을 원 단위로 반올림한 뒤 합산 슬리피지는 비용률에 넣지 않는다 — 실제 체결가를 쓰면 이미 손익에 반영되어 있고, `DAY_TRADE_COST_PCT`(수수료+슬리피지 추정치)는 진입/청산 **판정 게이트**용이라 기록에 쓰지 않는다. """ from __future__ import annotations def round_trip_costs( entry_price: float, exit_price: float, quantity: int, commission_rate: float, sell_tax_rate: float, ) -> dict: """왕복 매매 비용(원). `trade_helpers.calculate_trade_costs` 와 같은 반올림 규칙. Returns: {"buy_commission", "sell_commission", "sell_tax", "total_cost"} """ qty = max(0, int(quantity)) buy_commission = float(entry_price) * qty * float(commission_rate) sell_commission = float(exit_price) * qty * float(commission_rate) sell_tax = float(exit_price) * qty * float(sell_tax_rate) total_cost = buy_commission + sell_commission + sell_tax return { "buy_commission": round(buy_commission, 0), "sell_commission": round(sell_commission, 0), "sell_tax": round(sell_tax, 0), "total_cost": round(total_cost, 0), } def net_realized_pnl( entry_price: float, exit_price: float, quantity: int, commission_rate: float, sell_tax_rate: float, ) -> dict: """비용 차감 실현손익. Returns: { "gross_profit_loss": (청산가 − 진입가) × 수량, "total_cost": 왕복 비용(원), "net_profit_loss": gross − total_cost, "net_profit_loss_rate": net / (진입가 × 수량) × 100 (진입 원금 대비 %, 진입가<=0 이면 0.0), } """ qty = max(0, int(quantity)) gross = (float(exit_price) - float(entry_price)) * qty costs = round_trip_costs(entry_price, exit_price, qty, commission_rate, sell_tax_rate) net = gross - costs["total_cost"] invested = float(entry_price) * qty rate = (net / invested * 100.0) if invested > 0 else 0.0 return { "gross_profit_loss": gross, "total_cost": costs["total_cost"], "net_profit_loss": net, "net_profit_loss_rate": rate, }