fix(broker): get_latest_price uses market-data API (was always 0)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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:
parent
44132e9961
commit
c6ad39310c
2 changed files with 94 additions and 14 deletions
|
|
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue