feat(kevin-exec): attribute trades to strategy_id (record + dashboard)
The executor wrote trades with strategy_id=None, so Kevin trades were unattributed and invisible to the dashboard, which filters by strategy_id. Set strategy_id=signal.strategy_id on both the persisted Trade row and the published TradeExecution. [ci skip] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
14407d37dc
commit
a8b0d33bd1
2 changed files with 38 additions and 1 deletions
|
|
@ -183,7 +183,7 @@ async def process_signal(
|
||||||
price=result.filled_price or 0.0,
|
price=result.filled_price or 0.0,
|
||||||
status=result.status,
|
status=result.status,
|
||||||
signal_id=signal.signal_id,
|
signal_id=signal.signal_id,
|
||||||
strategy_id=None,
|
strategy_id=signal.strategy_id,
|
||||||
strategy_sources=signal.strategy_sources,
|
strategy_sources=signal.strategy_sources,
|
||||||
timestamp=result.timestamp,
|
timestamp=result.timestamp,
|
||||||
)
|
)
|
||||||
|
|
@ -210,6 +210,7 @@ async def process_signal(
|
||||||
price=result.filled_price or 0.0,
|
price=result.filled_price or 0.0,
|
||||||
timestamp=str(result.timestamp),
|
timestamp=str(result.timestamp),
|
||||||
signal_id=signal.signal_id,
|
signal_id=signal.signal_id,
|
||||||
|
strategy_id=signal.strategy_id,
|
||||||
status=status_map.get(result.status, TradeStatusModel.PENDING),
|
status=status_map.get(result.status, TradeStatusModel.PENDING),
|
||||||
)
|
)
|
||||||
session.add(db_trade)
|
session.add(db_trade)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import pytest
|
||||||
from services.trade_executor.config import TradeExecutorConfig
|
from services.trade_executor.config import TradeExecutorConfig
|
||||||
from services.trade_executor.main import process_signal
|
from services.trade_executor.main import process_signal
|
||||||
from services.trade_executor.risk_manager import RiskManager
|
from services.trade_executor.risk_manager import RiskManager
|
||||||
|
from shared.constants.kevin import KEVIN_STRATEGY_UUID
|
||||||
from shared.schemas.trading import (
|
from shared.schemas.trading import (
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
OrderResult,
|
OrderResult,
|
||||||
|
|
@ -73,6 +74,7 @@ def _make_kevin_signal(
|
||||||
target_dollars: Decimal | None = Decimal("2000"),
|
target_dollars: Decimal | None = Decimal("2000"),
|
||||||
stop_loss_pct: Decimal | None = Decimal("0.08"),
|
stop_loss_pct: Decimal | None = Decimal("0.08"),
|
||||||
take_profit_pct: Decimal | None = Decimal("0.20"),
|
take_profit_pct: Decimal | None = Decimal("0.20"),
|
||||||
|
strategy_id: UUID | None = KEVIN_STRATEGY_UUID,
|
||||||
) -> TradeSignal:
|
) -> TradeSignal:
|
||||||
"""A Kevin-style signal: price on the new ``current_price`` field,
|
"""A Kevin-style signal: price on the new ``current_price`` field,
|
||||||
pre-computed ``target_dollars``, and stop/take percentages — but NO
|
pre-computed ``target_dollars``, and stop/take percentages — but NO
|
||||||
|
|
@ -86,6 +88,7 @@ def _make_kevin_signal(
|
||||||
target_dollars=target_dollars,
|
target_dollars=target_dollars,
|
||||||
stop_loss_pct=stop_loss_pct,
|
stop_loss_pct=stop_loss_pct,
|
||||||
take_profit_pct=take_profit_pct,
|
take_profit_pct=take_profit_pct,
|
||||||
|
strategy_id=strategy_id,
|
||||||
timestamp=datetime.now(timezone.utc),
|
timestamp=datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -734,3 +737,36 @@ class TestExecutorDBPersistence:
|
||||||
# Verify signal_id is a UUID
|
# Verify signal_id is a UUID
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
assert isinstance(signal.signal_id, UUID)
|
assert isinstance(signal.signal_id, UUID)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExecutorStrategyAttribution:
|
||||||
|
"""Kevin trades must carry strategy_id so they are recorded as Kevin
|
||||||
|
trades and surface on the dashboard (which filters by strategy_id)."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_strategy_id_persisted_and_published(self):
|
||||||
|
config = _make_config()
|
||||||
|
broker = _mock_broker(positions=[], account=_make_account(100_000))
|
||||||
|
publisher = AsyncMock()
|
||||||
|
publisher.publish = AsyncMock(return_value=b"1-0")
|
||||||
|
counters = {
|
||||||
|
"trades_executed": MagicMock(),
|
||||||
|
"rejections": MagicMock(),
|
||||||
|
"fill_latency": MagicMock(),
|
||||||
|
}
|
||||||
|
|
||||||
|
signal = _make_kevin_signal()
|
||||||
|
mock_session = AsyncMock()
|
||||||
|
mock_session.add = MagicMock()
|
||||||
|
mock_session.commit = AsyncMock()
|
||||||
|
db_factory = _make_mock_db_session_factory(mock_session)
|
||||||
|
|
||||||
|
with patch.object(RiskManager, "check_risk", return_value=(True, "approved")):
|
||||||
|
await process_signal(
|
||||||
|
signal, RiskManager(config, broker), broker, publisher, counters, db_factory
|
||||||
|
)
|
||||||
|
|
||||||
|
trade_obj = mock_session.add.call_args[0][0]
|
||||||
|
assert trade_obj.strategy_id == KEVIN_STRATEGY_UUID
|
||||||
|
published = publisher.publish.call_args[0][0]
|
||||||
|
assert published["strategy_id"] == str(KEVIN_STRATEGY_UUID)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue