130 lines
4.1 KiB
Python
130 lines
4.1 KiB
Python
|
|
"""
|
||
|
|
Backtest API integration tests
|
||
|
|
"""
|
||
|
|
import pytest
|
||
|
|
from datetime import date
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from sqlalchemy.orm import Session
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.integration
|
||
|
|
class TestBacktestAPI:
|
||
|
|
"""Backtest API endpoint tests"""
|
||
|
|
|
||
|
|
def test_list_strategies(self, client: TestClient):
|
||
|
|
"""Test strategy list endpoint"""
|
||
|
|
response = client.get("/api/v1/backtest/strategies/list")
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
data = response.json()
|
||
|
|
assert "strategies" in data
|
||
|
|
assert len(data["strategies"]) > 0
|
||
|
|
|
||
|
|
# Check strategy structure
|
||
|
|
strategy = data["strategies"][0]
|
||
|
|
assert "name" in strategy
|
||
|
|
assert "description" in strategy
|
||
|
|
|
||
|
|
def test_run_backtest_invalid_dates(self, client: TestClient):
|
||
|
|
"""Test backtest with invalid date range"""
|
||
|
|
config = {
|
||
|
|
"name": "Invalid Date Test",
|
||
|
|
"strategy_name": "multi_factor",
|
||
|
|
"start_date": "2023-12-31",
|
||
|
|
"end_date": "2023-01-01", # End before start
|
||
|
|
"initial_capital": 10000000,
|
||
|
|
"commission_rate": 0.0015,
|
||
|
|
"rebalance_frequency": "monthly",
|
||
|
|
"strategy_config": {"count": 20}
|
||
|
|
}
|
||
|
|
|
||
|
|
response = client.post("/api/v1/backtest/run", json=config)
|
||
|
|
|
||
|
|
# Should fail validation
|
||
|
|
assert response.status_code in [400, 422]
|
||
|
|
|
||
|
|
def test_run_backtest_invalid_strategy(self, client: TestClient):
|
||
|
|
"""Test backtest with non-existent strategy"""
|
||
|
|
config = {
|
||
|
|
"name": "Invalid Strategy Test",
|
||
|
|
"strategy_name": "nonexistent_strategy",
|
||
|
|
"start_date": "2023-01-01",
|
||
|
|
"end_date": "2023-12-31",
|
||
|
|
"initial_capital": 10000000,
|
||
|
|
"commission_rate": 0.0015,
|
||
|
|
"rebalance_frequency": "monthly",
|
||
|
|
"strategy_config": {"count": 20}
|
||
|
|
}
|
||
|
|
|
||
|
|
response = client.post("/api/v1/backtest/run", json=config)
|
||
|
|
|
||
|
|
# Should fail with 400 or 404
|
||
|
|
assert response.status_code in [400, 404]
|
||
|
|
|
||
|
|
def test_run_backtest_missing_fields(self, client: TestClient):
|
||
|
|
"""Test backtest with missing required fields"""
|
||
|
|
config = {
|
||
|
|
"name": "Incomplete Test",
|
||
|
|
"strategy_name": "multi_factor",
|
||
|
|
# Missing dates and other required fields
|
||
|
|
}
|
||
|
|
|
||
|
|
response = client.post("/api/v1/backtest/run", json=config)
|
||
|
|
|
||
|
|
assert response.status_code == 422 # Validation error
|
||
|
|
|
||
|
|
@pytest.mark.slow
|
||
|
|
def test_run_backtest_success(
|
||
|
|
self,
|
||
|
|
client: TestClient,
|
||
|
|
sample_assets,
|
||
|
|
sample_price_data
|
||
|
|
):
|
||
|
|
"""Test successful backtest execution"""
|
||
|
|
config = {
|
||
|
|
"name": "Integration Test Backtest",
|
||
|
|
"strategy_name": "multi_factor",
|
||
|
|
"start_date": "2023-01-01",
|
||
|
|
"end_date": "2023-01-31",
|
||
|
|
"initial_capital": 10000000,
|
||
|
|
"commission_rate": 0.0015,
|
||
|
|
"rebalance_frequency": "monthly",
|
||
|
|
"strategy_config": {"count": 3}
|
||
|
|
}
|
||
|
|
|
||
|
|
response = client.post("/api/v1/backtest/run", json=config)
|
||
|
|
|
||
|
|
# Note: May fail if insufficient data, that's expected
|
||
|
|
if response.status_code == 200:
|
||
|
|
data = response.json()
|
||
|
|
assert "id" in data
|
||
|
|
assert "name" in data
|
||
|
|
assert "status" in data
|
||
|
|
assert data["name"] == config["name"]
|
||
|
|
|
||
|
|
def test_get_backtest_not_found(self, client: TestClient):
|
||
|
|
"""Test getting non-existent backtest"""
|
||
|
|
import uuid
|
||
|
|
fake_id = str(uuid.uuid4())
|
||
|
|
|
||
|
|
response = client.get(f"/api/v1/backtest/{fake_id}")
|
||
|
|
|
||
|
|
assert response.status_code == 404
|
||
|
|
|
||
|
|
def test_list_backtests(self, client: TestClient):
|
||
|
|
"""Test listing backtests"""
|
||
|
|
response = client.get("/api/v1/backtest/?skip=0&limit=10")
|
||
|
|
|
||
|
|
assert response.status_code == 200
|
||
|
|
data = response.json()
|
||
|
|
assert isinstance(data, list)
|
||
|
|
|
||
|
|
def test_delete_backtest_not_found(self, client: TestClient):
|
||
|
|
"""Test deleting non-existent backtest"""
|
||
|
|
import uuid
|
||
|
|
fake_id = str(uuid.uuid4())
|
||
|
|
|
||
|
|
response = client.delete(f"/api/v1/backtest/{fake_id}")
|
||
|
|
|
||
|
|
assert response.status_code == 404
|