341 lines
9.0 KiB
Markdown
341 lines
9.0 KiB
Markdown
# Trade Model Test Summary (Issue #6: DB-5)
|
|
|
|
## Overview
|
|
|
|
Comprehensive test suite for Trade model covering:
|
|
- Basic trade fields and enums
|
|
- CGT (Capital Gains Tax) calculations
|
|
- Multi-currency support
|
|
- Tax year handling (Australian FY)
|
|
- FIFO parcel matching
|
|
- Trade lifecycle management
|
|
|
|
## Test Files
|
|
|
|
### Unit Tests: `tests/unit/api/test_trade_model.py`
|
|
**65 tests** covering:
|
|
|
|
#### TestTradeBasicFields (4 tests)
|
|
- Create trade with required fields
|
|
- Default values (currency=AUD, fx_rate=1.0)
|
|
- All fields specified
|
|
- Timestamp auto-population
|
|
|
|
#### TestTradeSideEnum (3 tests)
|
|
- BUY side
|
|
- SELL side
|
|
- Invalid side rejection
|
|
|
|
#### TestTradeStatusEnum (5 tests)
|
|
- PENDING status
|
|
- FILLED status
|
|
- PARTIAL status
|
|
- CANCELLED status
|
|
- REJECTED status
|
|
|
|
#### TestTradeOrderTypeEnum (4 tests)
|
|
- MARKET order type
|
|
- LIMIT order type
|
|
- STOP order type
|
|
- STOP_LIMIT order type
|
|
|
|
#### TestTradeDecimalPrecision (7 tests)
|
|
- Quantity: Decimal(19,8) - supports crypto
|
|
- Price: Decimal(19,4)
|
|
- Total value: Decimal(19,4)
|
|
- CGT fields: Decimal(19,4)
|
|
- FX rate: Decimal(12,6)
|
|
- Signal confidence: Decimal(5,2) - range 0-100
|
|
|
|
#### TestTradeTaxYear (5 tests)
|
|
- FY2024 start (July 1, 2023)
|
|
- FY2024 end (June 30, 2024)
|
|
- FY2025 start (July 1, 2024)
|
|
- Before FY transition (June)
|
|
- After FY transition (July)
|
|
|
|
#### TestTradeCGTDiscount (4 tests)
|
|
- Not eligible: <367 days
|
|
- Eligible: exactly 367 days
|
|
- Eligible: >367 days
|
|
- Boundary: 366 days (not eligible)
|
|
|
|
#### TestTradeCGTCalculations (4 tests)
|
|
- Gross gain calculation
|
|
- Gross loss calculation
|
|
- Net gain with 50% discount
|
|
- Breakeven (no gain/loss)
|
|
|
|
#### TestTradeCurrencySupport (4 tests)
|
|
- Default AUD currency
|
|
- USD with FX rate conversion
|
|
- Common currency codes
|
|
- Currency uppercase enforcement
|
|
|
|
#### TestTradeConstraints (7 tests)
|
|
- Quantity must be > 0
|
|
- Quantity cannot be zero
|
|
- Price must be > 0
|
|
- Price cannot be zero
|
|
- Signal confidence: 0-100 range
|
|
- Signal confidence: >100 rejected
|
|
- Signal confidence: negative rejected
|
|
|
|
#### TestTradeSignalFields (3 tests)
|
|
- Signal source stored
|
|
- Signal confidence stored
|
|
- Signal fields optional
|
|
|
|
#### TestTradeProperties (4 tests)
|
|
- is_buy property (True for BUY)
|
|
- is_sell property (True for SELL)
|
|
- is_filled property (True for FILLED)
|
|
- is_filled False for PENDING
|
|
|
|
#### TestTradePortfolioRelationship (3 tests)
|
|
- Trade belongs to portfolio
|
|
- Portfolio has many trades
|
|
- Cascade delete with portfolio
|
|
|
|
#### TestTradeEdgeCases (6 tests)
|
|
- Very long symbol names
|
|
- Fractional shares (0.5)
|
|
- Very small quantities (crypto satoshis)
|
|
- Very large quantities (millions)
|
|
- Trade repr()
|
|
|
|
#### TestTradeQueryOperations (4 tests)
|
|
- Query by ID
|
|
- Filter by symbol
|
|
- Filter by side (BUY/SELL)
|
|
- Filter by status
|
|
|
|
### Integration Tests: `tests/integration/api/test_trade_integration.py`
|
|
**22 tests** covering:
|
|
|
|
#### TestTradePortfolioIntegration (4 tests)
|
|
- Create trade for portfolio
|
|
- Portfolio with multiple trades
|
|
- Cascade delete trades
|
|
- Multiple portfolios isolation
|
|
|
|
#### TestTradeCGTEndToEnd (3 tests)
|
|
- Simple buy-sell workflow
|
|
- Long-term hold with CGT discount
|
|
- Capital loss scenario
|
|
|
|
#### TestTradeFIFOMatching (3 tests)
|
|
- Single parcel full sale
|
|
- Multiple parcels - oldest first
|
|
- Partial parcel matching across buys
|
|
|
|
#### TestTradeMultiCurrency (3 tests)
|
|
- Foreign stock with FX conversion
|
|
- FX gain/loss in CGT calculation
|
|
- Mixed currency portfolio
|
|
|
|
#### TestTradeComplexQueries (5 tests)
|
|
- Aggregate position by symbol
|
|
- Query by tax year
|
|
- CGT discount eligibility filter
|
|
- Total CGT for year
|
|
- Order by date/value
|
|
|
|
#### TestTradeLifecycle (3 tests)
|
|
- Status progression (PENDING→PARTIAL→FILLED)
|
|
- Cancel pending order
|
|
- Reject invalid order
|
|
|
|
#### TestTradeReporting (2 tests)
|
|
- Portfolio performance metrics
|
|
- Symbol trading history
|
|
|
|
## Test Statistics
|
|
|
|
- **Total Tests**: 87 (65 unit + 22 integration)
|
|
- **All Tests**: SKIPPED (RED phase - implementation pending)
|
|
- **Expected Coverage**: 80%+ when implemented
|
|
|
|
## Key Test Patterns
|
|
|
|
### 1. TDD RED Phase
|
|
```python
|
|
try:
|
|
from spektiv.api.models.trade import Trade, TradeSide
|
|
# Test implementation
|
|
except ImportError:
|
|
pytest.skip("Trade model not yet implemented (TDD RED phase)")
|
|
```
|
|
|
|
### 2. Async Database Operations
|
|
```python
|
|
@pytest.mark.asyncio
|
|
async def test_create_trade(db_session, test_portfolio):
|
|
trade = Trade(portfolio_id=test_portfolio.id, ...)
|
|
db_session.add(trade)
|
|
await db_session.commit()
|
|
await db_session.refresh(trade)
|
|
```
|
|
|
|
### 3. Foreign Key Storage Pattern
|
|
```python
|
|
# Store foreign keys BEFORE async operations
|
|
portfolio_id = test_portfolio.id
|
|
# ... async operations ...
|
|
# Use stored ID to avoid lazy load after rollback
|
|
```
|
|
|
|
### 4. Constraint Testing
|
|
```python
|
|
with pytest.raises((IntegrityError, ValueError)):
|
|
trade = Trade(quantity=Decimal("-100")) # Invalid
|
|
await db_session.commit()
|
|
```
|
|
|
|
## Model Requirements (From Tests)
|
|
|
|
### Enums
|
|
```python
|
|
class TradeSide(Enum):
|
|
BUY = "BUY"
|
|
SELL = "SELL"
|
|
|
|
class TradeStatus(Enum):
|
|
PENDING = "PENDING"
|
|
FILLED = "FILLED"
|
|
PARTIAL = "PARTIAL"
|
|
CANCELLED = "CANCELLED"
|
|
REJECTED = "REJECTED"
|
|
|
|
class TradeOrderType(Enum):
|
|
MARKET = "MARKET"
|
|
LIMIT = "LIMIT"
|
|
STOP = "STOP"
|
|
STOP_LIMIT = "STOP_LIMIT"
|
|
```
|
|
|
|
### Required Fields
|
|
- portfolio_id (ForeignKey)
|
|
- symbol (String)
|
|
- side (TradeSide enum)
|
|
- quantity (Decimal(19,8))
|
|
- price (Decimal(19,4))
|
|
- order_type (TradeOrderType enum)
|
|
- status (TradeStatus enum)
|
|
- executed_at (DateTime, nullable for pending)
|
|
|
|
### Optional Fields
|
|
- total_value (Decimal(19,4))
|
|
- signal_source (String, nullable)
|
|
- signal_confidence (Decimal(5,2), 0-100 range, nullable)
|
|
- acquisition_date (Date, nullable)
|
|
- cost_basis_per_unit (Decimal(19,4), nullable)
|
|
- cost_basis_total (Decimal(19,4), nullable)
|
|
- holding_period_days (Integer, nullable)
|
|
- cgt_discount_eligible (Boolean, nullable)
|
|
- cgt_gross_gain (Decimal(19,4), nullable)
|
|
- cgt_gross_loss (Decimal(19,4), nullable)
|
|
- cgt_net_gain (Decimal(19,4), nullable)
|
|
- currency (String(3), default="AUD")
|
|
- fx_rate_to_aud (Decimal(12,6), default=1.0)
|
|
- total_value_aud (Decimal(19,4), nullable)
|
|
|
|
### Properties
|
|
- tax_year: String - Calculated from executed_at (Australian FY)
|
|
- is_buy: Boolean - True if side == BUY
|
|
- is_sell: Boolean - True if side == SELL
|
|
- is_filled: Boolean - True if status == FILLED
|
|
|
|
### Constraints
|
|
- quantity > 0
|
|
- price > 0
|
|
- signal_confidence: 0 <= value <= 100 (when not null)
|
|
- currency: uppercase, 3 letters
|
|
|
|
### Relationships
|
|
- portfolio: Many-to-One with Portfolio
|
|
- Cascade delete: trades deleted when portfolio deleted
|
|
- Back-populates: portfolio.trades
|
|
|
|
## Australian Tax Year Calculation
|
|
|
|
```python
|
|
# FY2024 = July 1, 2023 to June 30, 2024
|
|
if executed_at.month >= 7:
|
|
fy_year = executed_at.year + 1
|
|
else:
|
|
fy_year = executed_at.year
|
|
|
|
tax_year = f"FY{fy_year}"
|
|
```
|
|
|
|
## CGT Discount Eligibility
|
|
|
|
- **Eligible**: holding_period_days >= 367
|
|
- **Discount**: 50% of gross gain
|
|
- **Formula**: net_gain = gross_gain * 0.5 if eligible else gross_gain
|
|
|
|
## FIFO Matching Rules
|
|
|
|
1. Sell trades matched to oldest buy (by acquisition_date)
|
|
2. Partial parcel matching supported
|
|
3. Weighted average cost basis for multi-parcel sales
|
|
4. Holding period calculated from earliest acquisition
|
|
|
|
## Multi-Currency Support
|
|
|
|
- All trades stored in original currency
|
|
- FX rate at execution time stored
|
|
- AUD equivalent calculated for reporting
|
|
- CGT calculated in AUD (tax reporting currency)
|
|
|
|
## Next Steps (Implementation Phase)
|
|
|
|
1. Create `spektiv/api/models/trade.py`
|
|
2. Define enums (TradeSide, TradeStatus, TradeOrderType)
|
|
3. Create Trade model with all fields
|
|
4. Add check constraints
|
|
5. Add properties (tax_year, is_buy, is_sell, is_filled)
|
|
6. Add relationship to Portfolio
|
|
7. Create migration: `alembic revision --autogenerate -m "Add Trade model"`
|
|
8. Run tests: `pytest tests/unit/api/test_trade_model.py -v`
|
|
9. Run integration tests: `pytest tests/integration/api/test_trade_integration.py -v`
|
|
10. Verify 80%+ coverage
|
|
|
|
## Test Execution
|
|
|
|
```bash
|
|
# Run all trade tests
|
|
pytest tests/unit/api/test_trade_model.py tests/integration/api/test_trade_integration.py -v
|
|
|
|
# Run with coverage
|
|
pytest tests/unit/api/test_trade_model.py tests/integration/api/test_trade_integration.py --cov=spektiv/api/models/trade --cov-report=term-missing
|
|
|
|
# Run specific test class
|
|
pytest tests/unit/api/test_trade_model.py::TestTradeCGTCalculations -v
|
|
|
|
# Run with minimal verbosity (avoid pipe deadlock)
|
|
pytest tests/unit/api/test_trade_model.py --tb=line -q
|
|
```
|
|
|
|
## Coverage Goals
|
|
|
|
- **Unit Tests**: Basic CRUD, enums, constraints, properties
|
|
- **Integration Tests**: Relationships, CGT workflows, FIFO, multi-currency
|
|
- **Edge Cases**: Fractional shares, crypto quantities, long symbols
|
|
- **Boundary Tests**: CGT discount threshold (367 days), signal confidence (0-100)
|
|
|
|
## Related Issues
|
|
|
|
- **Issue #4 (DB-3)**: Portfolio model - parent relationship
|
|
- **Issue #6 (DB-5)**: Trade model - this test suite
|
|
- **Issue #7 (DB-6)**: Position model - will use trade data
|
|
- **Issue #8 (DB-7)**: Tax report - will aggregate CGT data
|
|
|
|
---
|
|
|
|
**Status**: ✅ Tests Complete (RED phase)
|
|
**Created**: 2025-12-26
|
|
**Tests**: 87 total (65 unit + 22 integration)
|
|
**Coverage**: Comprehensive (all requirements from spec)
|