fix(portfolio): refuse a portfolio with a number that is not finite (#1509)

- cash, quantities and average prices reject NaN and infinity when the file loads
This commit is contained in:
Yijia-Xiao
2026-10-27 07:58:35 +00:00
parent 62a06edb38
commit 3225a3c054
2 changed files with 19 additions and 1 deletions
+14
View File
@@ -96,6 +96,20 @@ def test_a_holding_keeps_the_instrument_it_was_listed_as():
assert "Other positions: WTI 100" in text and "CL=F" not in text
@pytest.mark.unit
@pytest.mark.parametrize("book", [
'{"cash": NaN}',
'{"positions": [{"ticker": "AAPL", "quantity": Infinity}]}',
'{"positions": [{"ticker": "AAPL", "quantity": 1, "average_price": "nan"}]}',
])
def test_load_rejects_a_number_that_is_not_finite(tmp_path, book):
# Python's json writes NaN and Infinity, and they would reach the agents as "nan units".
bad = tmp_path / "p.json"
bad.write_text(book)
with pytest.raises(ValueError, match="portfolio"):
load_portfolio(bad)
@pytest.mark.unit
def test_load_reads_a_valid_file(tmp_path):
good = tmp_path / "p.json"
+5 -1
View File
@@ -17,18 +17,22 @@ import hashlib
import json
from pathlib import Path
from pydantic import BaseModel, Field, ValidationError
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from tradingagents.dataflows.symbols import normalize_symbol
class Position(BaseModel):
model_config = ConfigDict(allow_inf_nan=False)
ticker: str = Field(description="Instrument symbol, e.g. AAPL")
quantity: float = Field(description="Signed units held; negative is short")
average_price: float | None = Field(default=None, description="Average entry price per unit")
class PortfolioContext(BaseModel):
model_config = ConfigDict(allow_inf_nan=False)
cash: float | None = Field(default=None, description="Free cash available")
currency: str | None = Field(default=None, description="Currency label for cash and prices")
positions: list[Position] = Field(default_factory=list)