fix(broker): get_latest_price uses market-data API (was always 0)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

get_latest_price queried TradingClient.get_asset (asset metadata — no price fields) so it ALWAYS returned Decimal('0'). Every Kevin signal was priced 0, so the executor skipped them ("No current price") even after the Phase 1 sizing fix; the market-closed deferral masked it until the market-open drain.

Use StockHistoricalDataClient.get_stock_latest_trade with a daily-bar close fallback — both return the last session's data, so they work when the market is closed (Kevin posts at all hours). Validated live: MRVL 263.16, AVGO 385.10 with the market closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-05 20:10:00 +00:00
parent 44132e9961
commit c6ad39310c
2 changed files with 94 additions and 14 deletions

View file

@ -164,13 +164,71 @@ def mock_client() -> MagicMock:
@pytest.fixture
def broker(mock_client: MagicMock) -> AlpacaBroker:
"""Return an ``AlpacaBroker`` whose internal client is mocked."""
with patch("shared.broker.alpaca_broker.TradingClient", return_value=mock_client):
def mock_data_client() -> MagicMock:
"""Return a mocked ``StockHistoricalDataClient`` (market-data API)."""
return MagicMock()
@pytest.fixture
def broker(mock_client: MagicMock, mock_data_client: MagicMock) -> AlpacaBroker:
"""Return an ``AlpacaBroker`` whose internal clients are mocked."""
with (
patch("shared.broker.alpaca_broker.TradingClient", return_value=mock_client),
patch(
"shared.broker.alpaca_broker.StockHistoricalDataClient",
return_value=mock_data_client,
),
):
b = AlpacaBroker(api_key="test-key", secret_key="test-secret", paper=True)
return b
# ---------------------------------------------------------------------------
# get_latest_price — market-data API (works when market closed)
# ---------------------------------------------------------------------------
class TestGetLatestPrice:
@pytest.mark.asyncio
async def test_returns_latest_trade_price(
self, broker: AlpacaBroker, mock_data_client: MagicMock
) -> None:
from decimal import Decimal
trade = MagicMock()
trade.price = 263.16
mock_data_client.get_stock_latest_trade.return_value = {"MRVL": trade}
price = await broker.get_latest_price("MRVL")
assert price == Decimal("263.16")
@pytest.mark.asyncio
async def test_falls_back_to_daily_bar_close(
self, broker: AlpacaBroker, mock_data_client: MagicMock
) -> None:
from decimal import Decimal
mock_data_client.get_stock_latest_trade.side_effect = Exception("no quote")
bar = MagicMock()
bar.close = 263.47
mock_data_client.get_stock_bars.return_value = MagicMock(data={"MRVL": [bar]})
price = await broker.get_latest_price("MRVL")
assert price == Decimal("263.47")
@pytest.mark.asyncio
async def test_returns_zero_when_all_lookups_fail(
self, broker: AlpacaBroker, mock_data_client: MagicMock
) -> None:
from decimal import Decimal
mock_data_client.get_stock_latest_trade.side_effect = Exception("x")
mock_data_client.get_stock_bars.side_effect = Exception("y")
price = await broker.get_latest_price("MRVL")
assert price == Decimal("0")
# ---------------------------------------------------------------------------
# Order submission
# ---------------------------------------------------------------------------