diff --git a/src/analysis/scanner.py b/src/analysis/scanner.py index 73882de..6bb384d 100644 --- a/src/analysis/scanner.py +++ b/src/analysis/scanner.py @@ -83,8 +83,8 @@ class MarketScanner: # Convert to orderbook-like structure orderbook = { "output1": { - "stck_prpr": price_data.get("output", {}).get("last", "0"), - "acml_vol": price_data.get("output", {}).get("tvol", "0"), + "stck_prpr": price_data.get("output", {}).get("last", "0") or "0", + "acml_vol": price_data.get("output", {}).get("tvol", "0") or "0", } } diff --git a/src/main.py b/src/main.py index b607c24..50f070a 100644 --- a/src/main.py +++ b/src/main.py @@ -107,7 +107,7 @@ async def trading_cycle( total_cash = float(balance_info.get("frcr_dncl_amt_2", "0") or "0") purchase_total = float(balance_info.get("frcr_buy_amt_smtl", "0") or "0") - current_price = float(price_data.get("output", {}).get("last", "0")) + current_price = float(price_data.get("output", {}).get("last", "0") or "0") foreigner_net = 0.0 # Not available for overseas # Calculate daily P&L % diff --git a/tests/test_main.py b/tests/test_main.py index 54d4d7e..be96bd5 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -394,6 +394,26 @@ class TestOverseasBalanceParsing: broker.get_overseas_balance = AsyncMock(return_value={"output2": []}) return broker + @pytest.fixture + def mock_overseas_broker_with_empty_price(self) -> MagicMock: + """Create mock overseas broker returning empty string for price.""" + broker = MagicMock() + broker.get_overseas_price = AsyncMock( + return_value={"output": {"last": ""}} # Empty string + ) + broker.get_overseas_balance = AsyncMock( + return_value={ + "output2": [ + { + "frcr_evlu_tota": "10000.00", + "frcr_dncl_amt_2": "5000.00", + "frcr_buy_amt_smtl": "4500.00", + } + ] + } + ) + return broker + @pytest.fixture def mock_domestic_broker(self) -> MagicMock: """Create minimal mock domestic broker.""" @@ -559,3 +579,37 @@ class TestOverseasBalanceParsing: # Verify balance API was called mock_overseas_broker_with_empty.get_overseas_balance.assert_called_once() + + @pytest.mark.asyncio + async def test_overseas_price_empty_string( + self, + mock_domestic_broker: MagicMock, + mock_overseas_broker_with_empty_price: MagicMock, + mock_brain_hold: MagicMock, + mock_risk: MagicMock, + mock_db: MagicMock, + mock_decision_logger: MagicMock, + mock_context_store: MagicMock, + mock_criticality_assessor: MagicMock, + mock_telegram: MagicMock, + mock_overseas_market: MagicMock, + ) -> None: + """Test overseas price parsing with empty string (issue #49).""" + with patch("src.main.log_trade"): + # Should not raise ValueError, should default to 0.0 + await trading_cycle( + broker=mock_domestic_broker, + overseas_broker=mock_overseas_broker_with_empty_price, + brain=mock_brain_hold, + risk=mock_risk, + db_conn=mock_db, + decision_logger=mock_decision_logger, + context_store=mock_context_store, + criticality_assessor=mock_criticality_assessor, + telegram=mock_telegram, + market=mock_overseas_market, + stock_code="AAPL", + ) + + # Verify price API was called + mock_overseas_broker_with_empty_price.get_overseas_price.assert_called_once() diff --git a/tests/test_volatility.py b/tests/test_volatility.py index b8f3a99..59a68fc 100644 --- a/tests/test_volatility.py +++ b/tests/test_volatility.py @@ -338,6 +338,28 @@ class TestMarketScanner: assert metrics.stock_code == "AAPL" assert metrics.current_price == 150.50 + @pytest.mark.asyncio + async def test_scan_stock_overseas_empty_price( + self, + scanner: MarketScanner, + mock_overseas_broker: OverseasBroker, + context_store: ContextStore, + ) -> None: + """Test scanning overseas stock with empty price string (issue #49).""" + mock_overseas_broker.get_overseas_price.return_value = { + "output": { + "last": "", # Empty string + "tvol": "", # Empty string + } + } + + market = MARKETS["US_NASDAQ"] + metrics = await scanner.scan_stock("AAPL", market) + + assert metrics is not None + assert metrics.stock_code == "AAPL" + assert metrics.current_price == 0.0 # Should default to 0.0 + @pytest.mark.asyncio async def test_scan_stock_error_handling( self,