fix: sua loi eviction dung nham gia cross-symbol trong trade_executor.py
Phat hien (p) khi viet test cho trade_executor: khi kiem tra hybrid eviction, PnL cua TAT CA cac trade dang mo (o nhieu symbol khac nhau) bi tinh bang current_price cua tin hieu dang xu ly, thay vi gia thuc cua tung symbol. Fix bang cach lookup gia moi nhat theo tung symbol/exchange/timeframe (batched query, cung pattern da dung dung trong close_stale_trades), ap dung cho ca xep hang loser LAN gia dong lenh cuoi cung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.exceptions import AppException
|
||||
from app.database import async_session_factory
|
||||
from app.models.candle import Candle
|
||||
from app.models.exchange import Exchange
|
||||
from app.models.real_trade import RealTrade
|
||||
from app.models.signal import HypotheticalTrade, Signal
|
||||
from app.models.symbol import Symbol
|
||||
from app.models.user import User
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
@@ -195,9 +198,42 @@ async def execute_signal_trade(
|
||||
|
||||
if open_count >= MAX_OPEN_TRADES:
|
||||
to_evict = open_count - MAX_OPEN_TRADES + 1
|
||||
|
||||
# Each open trade may be on a different symbol than the signal
|
||||
# currently being processed — using `current_price` (that
|
||||
# signal's price) for all of them would rank PnL against the
|
||||
# wrong market price. Look up each trade's own latest candle
|
||||
# price instead (batched by unique symbol/exchange/timeframe).
|
||||
trade_keys = list({(t.symbol, t.exchange, t.timeframe) for t in all_open_trades})
|
||||
price_map: dict[tuple[str, str, str], Decimal] = {}
|
||||
for sym, ex, tf in trade_keys:
|
||||
price_result = await db.execute(
|
||||
select(Candle.close)
|
||||
.select_from(Symbol)
|
||||
.join(Candle, Candle.symbol_id == Symbol.id)
|
||||
.join(Exchange, Exchange.id == Symbol.exchange_id)
|
||||
.where(and_(
|
||||
Exchange.name == ex,
|
||||
Symbol.symbol == sym,
|
||||
Candle.timeframe == tf,
|
||||
))
|
||||
.order_by(desc(Candle.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
row = price_result.first()
|
||||
if row:
|
||||
price_map[(sym, ex, tf)] = row[0]
|
||||
|
||||
def _price_for(t: HypotheticalTrade) -> Decimal:
|
||||
if t.symbol == symbol and t.exchange == exchange_name:
|
||||
return current_price
|
||||
# Fall back to entry_price (PnL=0, neutral) if no candle
|
||||
# data is available for this trade's own symbol.
|
||||
return price_map.get((t.symbol, t.exchange, t.timeframe), t.entry_price)
|
||||
|
||||
open_with_pnl = []
|
||||
for t in all_open_trades:
|
||||
pnl_val, _pct = _calculate_pnl(t.entry_price, current_price, t.direction, t.quantity)
|
||||
pnl_val, _pct = _calculate_pnl(t.entry_price, _price_for(t), t.direction, t.quantity)
|
||||
open_with_pnl.append((t, pnl_val))
|
||||
|
||||
losers = [(t, pnl) for t, pnl in open_with_pnl if pnl < 0]
|
||||
@@ -208,10 +244,11 @@ async def execute_signal_trade(
|
||||
eviction_candidates = all_open_trades[:to_evict]
|
||||
|
||||
for evict_trade in eviction_candidates:
|
||||
evict_price = _price_for(evict_trade)
|
||||
pnl, pnl_pct = _calculate_pnl(
|
||||
evict_trade.entry_price, current_price, evict_trade.direction, evict_trade.quantity
|
||||
evict_trade.entry_price, evict_price, evict_trade.direction, evict_trade.quantity
|
||||
)
|
||||
evict_trade.exit_price = current_price
|
||||
evict_trade.exit_price = evict_price
|
||||
evict_trade.exit_time = datetime.now(timezone.utc)
|
||||
evict_trade.exit_reason = "MAX_LIMIT_EVICT"
|
||||
evict_trade.pnl = pnl
|
||||
|
||||
Reference in New Issue
Block a user