"""Unit tests for Trade model (Issue #6: DB-5). Tests for Trade model fields including: - TradeSide enum (BUY, SELL) - TradeStatus enum (PENDING, FILLED, PARTIAL, CANCELLED, REJECTED) - TradeOrderType enum (MARKET, LIMIT, STOP, STOP_LIMIT) - Basic trade fields (symbol, quantity, price, etc.) - Signal fields (signal_source, signal_confidence) - CGT (Capital Gains Tax) fields and calculations - Currency support (currency, fx_rate_to_aud, total_value_aud) - Tax year calculation (Australian FY: July-June) - Decimal precision for monetary and quantity values - Check constraints (quantity > 0, price > 0, etc.) - Properties (is_buy, is_sell, is_filled) - Relationship with Portfolio Follows TDD principles with comprehensive coverage. Tests written BEFORE implementation (RED phase). """ import pytest from decimal import Decimal from datetime import datetime, date, timedelta from sqlalchemy import select from sqlalchemy.exc import IntegrityError # Mark all tests in this module as asyncio pytestmark = pytest.mark.asyncio class TestTradeBasicFields: """Tests for basic Trade model fields.""" @pytest.mark.asyncio async def test_create_trade_with_required_fields(self, db_session, test_portfolio): """Should create trade with only required fields.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert assert trade.id is not None assert trade.portfolio_id == test_portfolio.id assert trade.symbol == "AAPL" assert trade.side == TradeSide.BUY assert trade.quantity == Decimal("100") assert trade.price == Decimal("150.00") assert trade.order_type == TradeOrderType.MARKET assert trade.status == TradeStatus.FILLED except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_defaults(self, db_session, test_portfolio): """Should apply default values to optional fields.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.BUY, quantity=Decimal("50"), price=Decimal("200.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Check defaults assert trade.currency == "AUD" assert trade.fx_rate_to_aud == Decimal("1.0000") assert trade.created_at is not None assert trade.updated_at is not None except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_with_all_fields(self, db_session, test_portfolio): """Should create trade with all fields specified.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType executed_time = datetime(2024, 3, 15, 10, 30, 0) acquisition = date(2023, 6, 1) trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.SELL, quantity=Decimal("500"), price=Decimal("45.50"), total_value=Decimal("22750.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.FILLED, executed_at=executed_time, signal_source="TechnicalAnalysis", signal_confidence=Decimal("85.50"), acquisition_date=acquisition, cost_basis_per_unit=Decimal("40.00"), cost_basis_total=Decimal("20000.00"), holding_period_days=288, cgt_discount_eligible=False, cgt_gross_gain=Decimal("2750.00"), cgt_gross_loss=Decimal("0.00"), cgt_net_gain=Decimal("2750.00"), currency="AUD", fx_rate_to_aud=Decimal("1.0000"), total_value_aud=Decimal("22750.00"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert all fields assert trade.id is not None assert trade.symbol == "BHP.AX" assert trade.side == TradeSide.SELL assert trade.quantity == Decimal("500") assert trade.price == Decimal("45.50") assert trade.total_value == Decimal("22750.00") assert trade.signal_source == "TechnicalAnalysis" assert trade.signal_confidence == Decimal("85.50") assert trade.acquisition_date == acquisition assert trade.cgt_discount_eligible is False except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_timestamps_auto_populate(self, db_session, test_portfolio): """Should auto-populate created_at and updated_at timestamps.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="CBA.AX", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("95.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert timestamps exist and are recent assert trade.created_at is not None assert trade.updated_at is not None assert isinstance(trade.created_at, datetime) assert isinstance(trade.updated_at, datetime) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeSideEnum: """Tests for TradeSide enum validation.""" @pytest.mark.asyncio async def test_trade_side_buy(self, db_session, test_portfolio): """Should create trade with BUY side.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.side == TradeSide.BUY assert trade.side.value == "BUY" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_side_sell(self, db_session, test_portfolio): """Should create trade with SELL side.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.SELL, quantity=Decimal("50"), price=Decimal("200.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.side == TradeSide.SELL assert trade.side.value == "SELL" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_side_invalid_value(self, db_session, test_portfolio): """Should reject invalid trade side.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Try to create with invalid string with pytest.raises((ValueError, AttributeError)): trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side="INVALID_SIDE", quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeStatusEnum: """Tests for TradeStatus enum validation.""" @pytest.mark.asyncio async def test_trade_status_pending(self, db_session, test_portfolio): """Should create trade with PENDING status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.PENDING, executed_at=None, # Not executed yet ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.status == TradeStatus.PENDING assert trade.status.value == "PENDING" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_status_filled(self, db_session, test_portfolio): """Should create trade with FILLED status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.status == TradeStatus.FILLED assert trade.status.value == "FILLED" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_status_partial(self, db_session, test_portfolio): """Should create trade with PARTIAL status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.PARTIAL, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.status == TradeStatus.PARTIAL assert trade.status.value == "PARTIAL" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_status_cancelled(self, db_session, test_portfolio): """Should create trade with CANCELLED status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.CANCELLED, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.status == TradeStatus.CANCELLED assert trade.status.value == "CANCELLED" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_status_rejected(self, db_session, test_portfolio): """Should create trade with REJECTED status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.REJECTED, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.status == TradeStatus.REJECTED assert trade.status.value == "REJECTED" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeOrderTypeEnum: """Tests for TradeOrderType enum validation.""" @pytest.mark.asyncio async def test_order_type_market(self, db_session, test_portfolio): """Should create trade with MARKET order type.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.order_type == TradeOrderType.MARKET assert trade.order_type.value == "MARKET" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_order_type_limit(self, db_session, test_portfolio): """Should create trade with LIMIT order type.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.PENDING, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.order_type == TradeOrderType.LIMIT assert trade.order_type.value == "LIMIT" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_order_type_stop(self, db_session, test_portfolio): """Should create trade with STOP order type.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("140.00"), order_type=TradeOrderType.STOP, status=TradeStatus.PENDING, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.order_type == TradeOrderType.STOP assert trade.order_type.value == "STOP" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_order_type_stop_limit(self, db_session, test_portfolio): """Should create trade with STOP_LIMIT order type.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("140.00"), order_type=TradeOrderType.STOP_LIMIT, status=TradeStatus.PENDING, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.order_type == TradeOrderType.STOP_LIMIT assert trade.order_type.value == "STOP_LIMIT" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeDecimalPrecision: """Tests for Decimal precision on trade fields.""" @pytest.mark.asyncio async def test_quantity_decimal_precision(self, db_session, test_portfolio): """Should store quantity with 4 decimal places (PreciseNumeric standard).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="BTC", side=TradeSide.BUY, quantity=Decimal("0.1234"), # 4 decimal places (per model spec) price=Decimal("45000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert decimal precision maintained (4 decimals per PreciseNumeric) assert trade.quantity == Decimal("0.1234") assert isinstance(trade.quantity, Decimal) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_price_decimal_precision(self, db_session, test_portfolio): """Should store price with 4 decimal places.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.1234"), # 4 decimal places order_type=TradeOrderType.LIMIT, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert decimal precision maintained assert trade.price == Decimal("150.1234") assert isinstance(trade.price, Decimal) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_total_value_decimal_precision(self, db_session, test_portfolio): """Should store total_value with 4 decimal places.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.BUY, quantity=Decimal("50"), price=Decimal("200.00"), total_value=Decimal("10000.5678"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert decimal precision maintained assert trade.total_value == Decimal("10000.5678") assert isinstance(trade.total_value, Decimal) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_fields_decimal_precision(self, db_session, test_portfolio): """Should store CGT fields with 4 decimal places.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.SELL, quantity=Decimal("500"), price=Decimal("45.50"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), cost_basis_per_unit=Decimal("40.1234"), cost_basis_total=Decimal("20061.7000"), cgt_gross_gain=Decimal("2688.3000"), cgt_gross_loss=Decimal("0.0000"), cgt_net_gain=Decimal("2688.3000"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert CGT decimal precision assert trade.cost_basis_per_unit == Decimal("40.1234") assert trade.cost_basis_total == Decimal("20061.7000") assert trade.cgt_gross_gain == Decimal("2688.3000") assert trade.cgt_net_gain == Decimal("2688.3000") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_fx_rate_decimal_precision(self, db_session, test_portfolio): """Should store fx_rate_to_aud with 6 decimal places.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), currency="USD", fx_rate_to_aud=Decimal("1.523456"), # 6 decimal places ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert FX rate precision assert trade.fx_rate_to_aud == Decimal("1.523456") assert isinstance(trade.fx_rate_to_aud, Decimal) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_confidence_decimal_precision(self, db_session, test_portfolio): """Should store signal_confidence with 2 decimal places (0-100).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="ML_Model", signal_confidence=Decimal("87.65"), # 2 decimal places ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Assert signal confidence precision assert trade.signal_confidence == Decimal("87.65") assert isinstance(trade.signal_confidence, Decimal) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeTaxYear: """Tests for Australian tax year calculation (July-June).""" @pytest.mark.asyncio async def test_tax_year_fy2024_start(self, db_session, test_portfolio): """Should calculate tax year FY2024 for trade on July 1, 2023.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # July 1, 2023 starts FY2024 executed_time = datetime(2023, 7, 1, 10, 0, 0) trade = Trade( portfolio_id=test_portfolio.id, symbol="CBA.AX", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("95.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=executed_time, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # FY2024 = July 1, 2023 to June 30, 2024 assert trade.tax_year == "FY2024" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_tax_year_fy2024_end(self, db_session, test_portfolio): """Should calculate tax year FY2024 for trade on June 30, 2024.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # June 30, 2024 ends FY2024 executed_time = datetime(2024, 6, 30, 23, 59, 59) trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.BUY, quantity=Decimal("200"), price=Decimal("45.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=executed_time, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.tax_year == "FY2024" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_tax_year_fy2025_start(self, db_session, test_portfolio): """Should calculate tax year FY2025 for trade on July 1, 2024.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # July 1, 2024 starts FY2025 executed_time = datetime(2024, 7, 1, 0, 0, 0) trade = Trade( portfolio_id=test_portfolio.id, symbol="WES.AX", side=TradeSide.BUY, quantity=Decimal("150"), price=Decimal("55.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=executed_time, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.tax_year == "FY2025" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_tax_year_before_fy_transition(self, db_session, test_portfolio): """Should calculate tax year FY2024 for trade in June 2024.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # June 15, 2024 is before FY transition executed_time = datetime(2024, 6, 15, 14, 30, 0) trade = Trade( portfolio_id=test_portfolio.id, symbol="NAB.AX", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("30.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=executed_time, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.tax_year == "FY2024" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_tax_year_after_fy_transition(self, db_session, test_portfolio): """Should calculate tax year FY2025 for trade in July 2024.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # July 15, 2024 is after FY transition executed_time = datetime(2024, 7, 15, 9, 0, 0) trade = Trade( portfolio_id=test_portfolio.id, symbol="ANZ.AX", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("25.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=executed_time, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.tax_year == "FY2025" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeCGTDiscount: """Tests for CGT discount eligibility (367+ days).""" @pytest.mark.asyncio async def test_cgt_discount_not_eligible_short_hold(self, db_session, test_portfolio): """Should not be eligible for CGT discount with <367 days holding.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("160.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), acquisition_date=date.today() - timedelta(days=200), holding_period_days=200, cgt_discount_eligible=False, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.holding_period_days == 200 assert trade.cgt_discount_eligible is False except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_discount_eligible_367_days(self, db_session, test_portfolio): """Should be eligible for CGT discount with exactly 367 days holding.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.SELL, quantity=Decimal("500"), price=Decimal("45.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), acquisition_date=date.today() - timedelta(days=367), holding_period_days=367, cgt_discount_eligible=True, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.holding_period_days == 367 assert trade.cgt_discount_eligible is True except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_discount_eligible_long_hold(self, db_session, test_portfolio): """Should be eligible for CGT discount with >367 days holding.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="CBA.AX", side=TradeSide.SELL, quantity=Decimal("200"), price=Decimal("100.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), acquisition_date=date.today() - timedelta(days=500), holding_period_days=500, cgt_discount_eligible=True, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.holding_period_days == 500 assert trade.cgt_discount_eligible is True except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_discount_boundary_366_days(self, db_session, test_portfolio): """Should not be eligible with 366 days (boundary test).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="WES.AX", side=TradeSide.SELL, quantity=Decimal("150"), price=Decimal("55.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), acquisition_date=date.today() - timedelta(days=366), holding_period_days=366, cgt_discount_eligible=False, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.holding_period_days == 366 assert trade.cgt_discount_eligible is False except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeCGTCalculations: """Tests for CGT calculation fields.""" @pytest.mark.asyncio async def test_cgt_gross_gain_calculation(self, db_session, test_portfolio): """Should calculate gross gain correctly.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Sell at $50, cost basis $40 = $10 gain per unit # 100 units = $1000 gross gain trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("50.00"), total_value=Decimal("5000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), cost_basis_per_unit=Decimal("40.00"), cost_basis_total=Decimal("4000.00"), cgt_gross_gain=Decimal("1000.00"), cgt_gross_loss=Decimal("0.00"), cgt_net_gain=Decimal("1000.00"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.cgt_gross_gain == Decimal("1000.00") assert trade.cgt_gross_loss == Decimal("0.00") assert trade.cgt_net_gain == Decimal("1000.00") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_gross_loss_calculation(self, db_session, test_portfolio): """Should calculate gross loss correctly.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Sell at $30, cost basis $40 = $10 loss per unit # 100 units = $1000 gross loss trade = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("30.00"), total_value=Decimal("3000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), cost_basis_per_unit=Decimal("40.00"), cost_basis_total=Decimal("4000.00"), cgt_gross_gain=Decimal("0.00"), cgt_gross_loss=Decimal("1000.00"), cgt_net_gain=Decimal("-1000.00"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.cgt_gross_gain == Decimal("0.00") assert trade.cgt_gross_loss == Decimal("1000.00") assert trade.cgt_net_gain == Decimal("-1000.00") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_net_gain_with_discount(self, db_session, test_portfolio): """Should calculate net gain with CGT discount applied.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # $1000 gross gain, eligible for 50% discount = $500 net gain trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("50.00"), total_value=Decimal("5000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), acquisition_date=date.today() - timedelta(days=400), cost_basis_per_unit=Decimal("40.00"), cost_basis_total=Decimal("4000.00"), holding_period_days=400, cgt_discount_eligible=True, cgt_gross_gain=Decimal("1000.00"), cgt_gross_loss=Decimal("0.00"), cgt_net_gain=Decimal("500.00"), # 50% discount applied ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.cgt_gross_gain == Decimal("1000.00") assert trade.cgt_discount_eligible is True assert trade.cgt_net_gain == Decimal("500.00") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cgt_no_gain_or_loss(self, db_session, test_portfolio): """Should handle breakeven trades (no gain or loss).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Sell at cost basis = no gain or loss trade = Trade( portfolio_id=test_portfolio.id, symbol="CBA.AX", side=TradeSide.SELL, quantity=Decimal("100"), price=Decimal("40.00"), total_value=Decimal("4000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), cost_basis_per_unit=Decimal("40.00"), cost_basis_total=Decimal("4000.00"), cgt_gross_gain=Decimal("0.00"), cgt_gross_loss=Decimal("0.00"), cgt_net_gain=Decimal("0.00"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.cgt_gross_gain == Decimal("0.00") assert trade.cgt_gross_loss == Decimal("0.00") assert trade.cgt_net_gain == Decimal("0.00") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeCurrencySupport: """Tests for multi-currency support.""" @pytest.mark.asyncio async def test_default_currency_aud(self, db_session, test_portfolio): """Should default to AUD currency.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="BHP.AX", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("45.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.currency == "AUD" assert trade.fx_rate_to_aud == Decimal("1.0000") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_usd_currency_with_fx_rate(self, db_session, test_portfolio): """Should support USD with FX rate conversion.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # $100 USD @ 1.50 FX rate = $150 AUD trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), total_value=Decimal("15000.00"), # USD order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), currency="USD", fx_rate_to_aud=Decimal("1.50"), total_value_aud=Decimal("22500.00"), # AUD ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.currency == "USD" assert trade.fx_rate_to_aud == Decimal("1.50") assert trade.total_value == Decimal("15000.00") assert trade.total_value_aud == Decimal("22500.00") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_common_currencies(self, db_session, test_portfolio): """Should accept common 3-letter currency codes.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType currencies = ["USD", "EUR", "GBP", "JPY", "CNY", "AUD"] for currency in currencies: trade = Trade( portfolio_id=test_portfolio.id, symbol=f"STOCK.{currency}", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("100.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), currency=currency, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.currency == currency except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_currency_uppercase_enforced(self, db_session, test_portfolio): """Should store currency in uppercase.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), currency="usd", # lowercase input ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Should be stored in uppercase assert trade.currency == "USD" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeConstraints: """Tests for CheckConstraints on trade fields.""" @pytest.mark.asyncio async def test_quantity_must_be_positive(self, db_session, test_portfolio): """Should reject negative quantity.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("-100"), # Negative! price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_quantity_cannot_be_zero(self, db_session, test_portfolio): """Should reject zero quantity.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("0"), # Zero! price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_price_must_be_positive(self, db_session, test_portfolio): """Should reject negative price.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("-150.00"), # Negative! order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_price_cannot_be_zero(self, db_session, test_portfolio): """Should reject zero price.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("0.00"), # Zero! order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_confidence_range_0_to_100(self, db_session, test_portfolio): """Should accept signal_confidence between 0 and 100.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Test boundary values for confidence in [Decimal("0.00"), Decimal("50.00"), Decimal("100.00")]: trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="Test", signal_confidence=confidence, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.signal_confidence == confidence except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_confidence_above_100_rejected(self, db_session, test_portfolio): """Should reject signal_confidence > 100.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="Test", signal_confidence=Decimal("101.00"), # > 100! ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_confidence_negative_rejected(self, db_session, test_portfolio): """Should reject negative signal_confidence.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="Test", signal_confidence=Decimal("-10.00"), # Negative! ) db_session.add(trade) with pytest.raises((IntegrityError, ValueError)): await db_session.commit() except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeSignalFields: """Tests for signal tracking fields.""" @pytest.mark.asyncio async def test_signal_source_stored(self, db_session, test_portfolio): """Should store signal source string.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="TechnicalAnalysis", ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.signal_source == "TechnicalAnalysis" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_confidence_stored(self, db_session, test_portfolio): """Should store signal confidence value.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), signal_source="ML_Model", signal_confidence=Decimal("92.50"), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.signal_confidence == Decimal("92.50") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_signal_fields_optional(self, db_session, test_portfolio): """Should allow trades without signal fields.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), # No signal_source or signal_confidence ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.signal_source is None assert trade.signal_confidence is None except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeProperties: """Tests for trade model properties.""" @pytest.mark.asyncio async def test_is_buy_property(self, db_session, test_portfolio): """Should return True for BUY side.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.is_buy is True assert trade.is_sell is False except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_is_sell_property(self, db_session, test_portfolio): """Should return True for SELL side.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.SELL, quantity=Decimal("50"), price=Decimal("200.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.is_buy is False assert trade.is_sell is True except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_is_filled_property(self, db_session, test_portfolio): """Should return True for FILLED status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.is_filled is True except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_is_filled_false_for_pending(self, db_session, test_portfolio): """Should return False for PENDING status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.PENDING, executed_at=None, ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.is_filled is False except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradePortfolioRelationship: """Tests for Trade-Portfolio relationship.""" @pytest.mark.asyncio async def test_trade_belongs_to_portfolio(self, db_session, test_portfolio): """Should establish relationship from trade to portfolio.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) # Load the relationship await db_session.refresh(trade, ["portfolio"]) assert trade.portfolio is not None assert trade.portfolio.id == test_portfolio.id assert trade.portfolio.name == test_portfolio.name except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_portfolio_has_many_trades(self, db_session, test_portfolio): """Should establish relationship from portfolio to trades.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade1 = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) trade2 = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.BUY, quantity=Decimal("50"), price=Decimal("200.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade1) db_session.add(trade2) await db_session.commit() # Refresh portfolio with trades relationship await db_session.refresh(test_portfolio, ["trades"]) assert len(test_portfolio.trades) == 2 symbols = [t.symbol for t in test_portfolio.trades] assert "AAPL" in symbols assert "TSLA" in symbols except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_cascade_delete_when_portfolio_deleted(self, db_session, test_user): """Should delete trades when portfolio is deleted (cascade).""" try: from tradingagents.api.models.portfolio import Portfolio, PortfolioType from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Create a temporary portfolio portfolio = Portfolio( user_id=test_user.id, name="Temp Portfolio", portfolio_type=PortfolioType.PAPER, initial_capital=Decimal("10000.00"), ) db_session.add(portfolio) await db_session.commit() await db_session.refresh(portfolio) # Create trades trade = Trade( portfolio_id=portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() trade_id = trade.id # Delete the portfolio await db_session.delete(portfolio) await db_session.commit() # Check trade is also deleted result = await db_session.execute( select(Trade).where(Trade.id == trade_id) ) deleted_trade = result.scalar_one_or_none() assert deleted_trade is None except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeEdgeCases: """Tests for edge cases and boundary conditions.""" @pytest.mark.asyncio async def test_very_long_symbol(self, db_session, test_portfolio): """Should handle symbol names up to 20 chars (model limit).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType long_symbol = "A" * 20 # 20 char limit per model spec trade = Trade( portfolio_id=test_portfolio.id, symbol=long_symbol, side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.symbol == long_symbol except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_fractional_shares(self, db_session, test_portfolio): """Should handle fractional share quantities.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("0.5"), # Half a share price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.quantity == Decimal("0.5") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_very_small_quantity(self, db_session, test_portfolio): """Should handle small quantities within 4 decimal precision.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="BTC", side=TradeSide.BUY, quantity=Decimal("0.0001"), # Smallest with 4 decimal precision price=Decimal("45000.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.quantity == Decimal("0.0001") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_very_large_quantity(self, db_session, test_portfolio): """Should handle large quantities.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="PENNY_STOCK", side=TradeSide.BUY, quantity=Decimal("1000000"), # 1 million shares price=Decimal("0.01"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) assert trade.quantity == Decimal("1000000") except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_trade_repr(self, db_session, test_portfolio): """Should have meaningful string representation.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() await db_session.refresh(trade) repr_str = repr(trade) assert "Trade" in repr_str assert "AAPL" in repr_str or str(trade.id) in repr_str except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") class TestTradeQueryOperations: """Tests for querying Trade records.""" @pytest.mark.asyncio async def test_query_trade_by_id(self, db_session, test_portfolio): """Should retrieve trade by ID.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() trade_id = trade.id # Query by ID result = await db_session.execute( select(Trade).where(Trade.id == trade_id) ) found = result.scalar_one() assert found.id == trade_id assert found.symbol == "AAPL" except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_query_trades_by_symbol(self, db_session, test_portfolio): """Should filter trades by symbol.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Create trades for different symbols symbols = ["AAPL", "TSLA", "AAPL", "GOOGL"] for symbol in symbols: trade = Trade( portfolio_id=test_portfolio.id, symbol=symbol, side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add(trade) await db_session.commit() # Query for AAPL trades result = await db_session.execute( select(Trade).where(Trade.symbol == "AAPL") ) aapl_trades = result.scalars().all() assert len(aapl_trades) == 2 assert all(t.symbol == "AAPL" for t in aapl_trades) except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_query_trades_by_side(self, db_session, test_portfolio): """Should filter trades by side (BUY/SELL).""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Create mix of BUY and SELL trades buy_trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) sell_trade = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.SELL, quantity=Decimal("50"), price=Decimal("160.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) db_session.add_all([buy_trade, sell_trade]) await db_session.commit() # Query for BUY trades result = await db_session.execute( select(Trade).where(Trade.side == TradeSide.BUY) ) buy_trades = result.scalars().all() assert len(buy_trades) == 1 assert buy_trades[0].side == TradeSide.BUY except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)") @pytest.mark.asyncio async def test_query_trades_by_status(self, db_session, test_portfolio): """Should filter trades by status.""" try: from tradingagents.api.models.trade import Trade, TradeSide, TradeStatus, TradeOrderType # Create trades with different statuses filled = Trade( portfolio_id=test_portfolio.id, symbol="AAPL", side=TradeSide.BUY, quantity=Decimal("100"), price=Decimal("150.00"), order_type=TradeOrderType.MARKET, status=TradeStatus.FILLED, executed_at=datetime.utcnow(), ) pending = Trade( portfolio_id=test_portfolio.id, symbol="TSLA", side=TradeSide.BUY, quantity=Decimal("50"), price=Decimal("200.00"), order_type=TradeOrderType.LIMIT, status=TradeStatus.PENDING, executed_at=None, ) db_session.add_all([filled, pending]) await db_session.commit() # Query for PENDING trades result = await db_session.execute( select(Trade).where(Trade.status == TradeStatus.PENDING) ) pending_trades = result.scalars().all() assert len(pending_trades) == 1 assert pending_trades[0].status == TradeStatus.PENDING except ImportError: pytest.skip("Trade model not yet implemented (TDD RED phase)")