feat(kevin): correct exits, realized P&L, wire exit scanner
- executor: EXIT/SELL signals close the FULL held broker position (not a target_dollars-sized fresh order) and skip when flat - executor: book realized P&L on the closing trade ((fill - avg_entry)*qty) so the dashboard P&L + win-rate populate; entries leave pnl=None - exit scanner: wired into the bridge run loop on kevin_bridge_exit_scan_cron (daily ET gate; croniter intentionally not a dependency) plus an offsetting-SELL guard so it only emits exits for currently-held tickers [ci skip] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a8b0d33bd1
commit
52b3c76482
7 changed files with 587 additions and 15 deletions
|
|
@ -27,15 +27,26 @@ class ExitScanner:
|
|||
session_factory: Callable[..., Any],
|
||||
publisher: Any,
|
||||
config: Any,
|
||||
broker: Any,
|
||||
) -> None:
|
||||
self.session_factory = session_factory
|
||||
self.publisher = publisher
|
||||
self.config = config
|
||||
self.broker = broker
|
||||
|
||||
async def scan_and_emit_exits(self) -> int:
|
||||
"""Returns the number of EXIT signals emitted."""
|
||||
now = datetime.now(timezone.utc)
|
||||
emitted = 0
|
||||
|
||||
# Offsetting-SELL guard: only emit exits for tickers STILL held at the
|
||||
# broker, so we never re-emit for an already-closed position. With zero
|
||||
# open positions this set is empty → the scan is a safe no-op.
|
||||
positions = await self.broker.get_positions()
|
||||
held_tickers = {p.ticker for p in positions if p.qty != 0}
|
||||
if not held_tickers:
|
||||
return 0
|
||||
|
||||
async with self.session_factory() as session:
|
||||
# Find open Kevin trades (FILLED, no closing trade yet on same ticker)
|
||||
open_trades = (
|
||||
|
|
@ -54,6 +65,9 @@ class ExitScanner:
|
|||
)
|
||||
|
||||
for trade in open_trades:
|
||||
# Skip tickers no longer held at the broker (already closed).
|
||||
if trade.ticker not in held_tickers:
|
||||
continue
|
||||
# Find the source audit row to learn the original holding_days target
|
||||
async with self.session_factory() as session:
|
||||
audit = (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue