feat(kevin-exec): size from target_dollars, propagate price, bracket entries
Kevin signals never placed orders: the executor sized only from sentiment_context["current_price"] (None for Kevin) so qty=0, and orders were always built SIMPLE (stop/take pcts ignored). - TradeSignal gains `current_price`; the bridge now sets it on publish - risk_manager honors `target_dollars` directly (no strength re-scale) and resolves price from current_price then sentiment_context - executor builds BRACKET orders for LONG entries carrying stop/take pcts; EXIT/SELL signals stay SIMPLE (the bridge sets pcts even on exits) [ci skip] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7d1d4464c9
commit
14407d37dc
6 changed files with 302 additions and 26 deletions
|
|
@ -118,6 +118,65 @@ async def test_bridge_kill_switch_on_publishes_to_stream():
|
|||
cursor.advance.assert_awaited_with(1)
|
||||
|
||||
|
||||
async def test_bridge_attaches_current_price_to_signal():
|
||||
"""The bridge must propagate broker.get_latest_price onto the published
|
||||
TradeSignal so the executor can size the position."""
|
||||
config = MagicMock(
|
||||
kevin_enable_trading=True,
|
||||
kevin_stop_loss_pct=0.08,
|
||||
kevin_take_profit_pct=0.20,
|
||||
kevin_avoid_blocks_days=7,
|
||||
)
|
||||
cursor = AsyncMock()
|
||||
cursor.last_seen_id.return_value = 0
|
||||
publisher = AsyncMock()
|
||||
publisher.publish.return_value = "1234-0"
|
||||
aggregator = AsyncMock()
|
||||
aggregator.fetch_pending.return_value = [
|
||||
MagicMock(
|
||||
id=1,
|
||||
symbol="NVDA",
|
||||
action=MagicMock(value="buy"),
|
||||
conviction=Decimal("0.8"),
|
||||
effective_conviction=Decimal("0.8"),
|
||||
time_horizon=MagicMock(value="weeks"),
|
||||
),
|
||||
]
|
||||
strategy = AsyncMock()
|
||||
from shared.schemas.kevin import KevinDecisionType
|
||||
strategy.evaluate_mention.return_value = MagicMock(
|
||||
decision=KevinDecisionType.OPEN_LONG,
|
||||
symbol="NVDA",
|
||||
target_dollars=Decimal("3000"),
|
||||
holding_days=10,
|
||||
effective_conviction=Decimal("0.8"),
|
||||
rationale="ok",
|
||||
)
|
||||
audit_writer = AsyncMock()
|
||||
broker = AsyncMock()
|
||||
broker.is_asset_tradable.return_value = True
|
||||
broker.get_latest_price.return_value = Decimal("123.45")
|
||||
broker.get_account.return_value = MagicMock(
|
||||
equity=Decimal("100000"), cash=Decimal("100000")
|
||||
)
|
||||
broker.get_positions.return_value = []
|
||||
|
||||
bridge = KevinBridge(
|
||||
config=config,
|
||||
cursor=cursor,
|
||||
publisher=publisher,
|
||||
aggregator=aggregator,
|
||||
strategy=strategy,
|
||||
audit_writer=audit_writer,
|
||||
broker=broker,
|
||||
)
|
||||
await bridge.process_one_pass()
|
||||
|
||||
publisher.publish.assert_awaited_once()
|
||||
published_signal = publisher.publish.call_args[0][0]
|
||||
assert published_signal.current_price == Decimal("123.45")
|
||||
|
||||
|
||||
async def test_bridge_advances_cursor_only_after_publish():
|
||||
"""Race-condition guard: cursor must NOT advance if publish raises."""
|
||||
config = MagicMock(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue