mirror of
https://github.com/saymrwulf/alpha-arena.git
synced 2026-05-14 20:37:51 +00:00
A comprehensive autonomous trading system for Polymarket prediction markets featuring multi-LLM provider support, a native macOS menu bar app, and a web-based control dashboard. Key features: - Multi-agent trading system (Research, Risk, Execution, Reflection agents) - LLM provider flexibility (Anthropic, OpenAI, Google, xAI, Local models) - Automatic provider fallback chain for resilience - Native Swift/SwiftUI macOS menu bar application - FastAPI web dashboard with real-time WebSocket updates - Risk management with kill switch - Technical indicators and market analysis
357 lines
12 KiB
Python
357 lines
12 KiB
Python
"""
|
|
End-to-End (E2E) Functional Tests for Alpha Arena.
|
|
|
|
These tests verify complete user workflows and system integration.
|
|
They test the application from the user's perspective.
|
|
"""
|
|
|
|
import asyncio
|
|
import pytest
|
|
from decimal import Decimal
|
|
|
|
pytestmark = [pytest.mark.e2e, pytest.mark.slow]
|
|
|
|
|
|
class TestTradingWorkflow:
|
|
"""Test complete trading workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_simulation_trading_cycle(self, async_client):
|
|
"""
|
|
Complete workflow: Check status -> Start simulation -> Check running -> Stop.
|
|
"""
|
|
# Step 1: Check initial status
|
|
response = await async_client.get("/api/trading/status")
|
|
assert response.status_code == 200
|
|
initial_status = response.json()
|
|
|
|
# Step 2: Start trading in simulation mode
|
|
response = await async_client.post(
|
|
"/api/trading/start",
|
|
json={"mode": "simulation", "loop_interval": 5, "max_iterations": 1}
|
|
)
|
|
# May already be running or succeed
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert data.get("status") in ["started", "already_running"]
|
|
|
|
# Step 3: Verify status changed
|
|
response = await async_client.get("/api/trading/status")
|
|
assert response.status_code == 200
|
|
|
|
# Step 4: Stop trading
|
|
response = await async_client.post("/api/trading/stop")
|
|
# Should succeed or say not running
|
|
assert response.status_code in [200, 400]
|
|
|
|
# Step 5: Verify stopped
|
|
response = await async_client.get("/api/trading/status")
|
|
assert response.status_code == 200
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kill_switch_emergency_stop(self, async_client):
|
|
"""
|
|
Test emergency stop workflow: Start -> Activate kill switch -> Verify blocked.
|
|
"""
|
|
# Ensure kill switch is off initially
|
|
status_response = await async_client.get("/api/trading/status")
|
|
initial_state = status_response.json().get("kill_switch", False)
|
|
|
|
# Activate kill switch (API expects form data)
|
|
response = await async_client.post(
|
|
"/api/trading/kill-switch",
|
|
data={"enabled": "true"}
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data.get("kill_switch") is True
|
|
|
|
# Verify trading is blocked if kill switch is on
|
|
start_response = await async_client.post(
|
|
"/api/trading/start",
|
|
json={"mode": "simulation"}
|
|
)
|
|
# Should fail or indicate kill switch active
|
|
assert start_response.status_code in [200, 400]
|
|
|
|
# Turn off kill switch
|
|
response = await async_client.post(
|
|
"/api/trading/kill-switch",
|
|
data={"enabled": "false"}
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestPositionManagementWorkflow:
|
|
"""Test position and order management workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_view_positions_workflow(self, async_client):
|
|
"""
|
|
Workflow: View positions -> Check details -> View orders.
|
|
"""
|
|
# Step 1: Get all positions
|
|
response = await async_client.get("/api/positions")
|
|
assert response.status_code == 200
|
|
positions = response.json().get("positions", [])
|
|
|
|
# Step 2: Get all orders
|
|
response = await async_client.get("/api/orders")
|
|
assert response.status_code == 200
|
|
orders = response.json().get("orders", [])
|
|
|
|
# Step 3: Page should render with this data
|
|
response = await async_client.get("/positions")
|
|
assert response.status_code == 200
|
|
assert "text/html" in response.headers.get("content-type", "")
|
|
|
|
|
|
class TestMarketAnalysisWorkflow:
|
|
"""Test market browsing and analysis workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_browse_and_filter_markets(self, async_client):
|
|
"""
|
|
Workflow: Browse markets -> Filter -> View details.
|
|
"""
|
|
# Step 1: Get all markets
|
|
response = await async_client.get("/api/markets")
|
|
assert response.status_code == 200
|
|
markets = response.json().get("markets", [])
|
|
|
|
# Step 2: Filter by category (if supported)
|
|
response = await async_client.get("/api/markets?limit=10")
|
|
assert response.status_code == 200
|
|
|
|
# Step 3: Get arbitrage opportunities
|
|
response = await async_client.get("/api/arbitrage")
|
|
assert response.status_code == 200
|
|
opportunities = response.json().get("opportunities", [])
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_market_detail_view(self, async_client):
|
|
"""
|
|
Workflow: List markets -> Select one -> View details.
|
|
"""
|
|
# Get markets
|
|
response = await async_client.get("/api/markets?limit=5")
|
|
assert response.status_code == 200
|
|
markets = response.json().get("markets", [])
|
|
|
|
if markets and "id" in markets[0]:
|
|
market_id = markets[0]["id"]
|
|
# Get detail
|
|
response = await async_client.get(f"/api/markets/{market_id}")
|
|
# May or may not exist
|
|
assert response.status_code in [200, 404]
|
|
|
|
|
|
class TestWalletAnalysisWorkflow:
|
|
"""Test wallet analysis workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_wallet_workflow(self, async_client):
|
|
"""
|
|
Workflow: Enter address -> Analyze -> View results.
|
|
"""
|
|
# Step 1: Page should render
|
|
response = await async_client.get("/wallet-analysis")
|
|
assert response.status_code == 200
|
|
|
|
# Step 2: Analyze a wallet (with valid format)
|
|
test_address = "0x1234567890123456789012345678901234567890"
|
|
response = await async_client.post(
|
|
"/api/wallet/analyze",
|
|
json={"address": test_address}
|
|
)
|
|
# May fail due to no real data, but should handle gracefully
|
|
assert response.status_code in [200, 404, 500]
|
|
|
|
|
|
class TestRiskManagementWorkflow:
|
|
"""Test risk management workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_view_and_update_risk_settings(self, async_client):
|
|
"""
|
|
Workflow: View risk status -> Update settings -> Verify changes.
|
|
"""
|
|
# Step 1: Get current risk status
|
|
response = await async_client.get("/api/risk/status")
|
|
assert response.status_code == 200
|
|
initial_status = response.json()
|
|
|
|
# Step 2: Update risk settings
|
|
new_settings = {
|
|
"max_position_size_usdc": 200,
|
|
"daily_loss_limit_usdc": 100
|
|
}
|
|
response = await async_client.post("/api/risk/settings", json=new_settings)
|
|
# May succeed or fail validation
|
|
assert response.status_code in [200, 400, 422]
|
|
|
|
# Step 3: Verify page renders
|
|
response = await async_client.get("/risk")
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestConfigurationWorkflow:
|
|
"""Test configuration management workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_view_and_update_config(self, async_client):
|
|
"""
|
|
Workflow: View config -> Update -> Verify.
|
|
"""
|
|
# Step 1: Get current config
|
|
response = await async_client.get("/api/config")
|
|
assert response.status_code == 200
|
|
initial_config = response.json()
|
|
|
|
# Step 2: Update config
|
|
response = await async_client.post(
|
|
"/api/config",
|
|
json={"loop_interval_seconds": 90}
|
|
)
|
|
assert response.status_code in [200, 400]
|
|
|
|
# Step 3: Verify page renders
|
|
response = await async_client.get("/config")
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestLogsAndMetricsWorkflow:
|
|
"""Test logs and metrics viewing workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_view_decision_logs(self, async_client):
|
|
"""
|
|
Workflow: View logs -> Filter -> Check metrics.
|
|
"""
|
|
# Step 1: Get decision logs
|
|
response = await async_client.get("/api/logs/decisions?limit=50")
|
|
assert response.status_code == 200
|
|
logs = response.json().get("logs", [])
|
|
|
|
# Step 2: Get trade history
|
|
response = await async_client.get("/api/logs/trades")
|
|
assert response.status_code == 200
|
|
|
|
# Step 3: Get performance metrics
|
|
response = await async_client.get("/api/metrics/performance")
|
|
assert response.status_code == 200
|
|
|
|
# Step 4: Get equity curve
|
|
response = await async_client.get("/api/metrics/equity-curve")
|
|
assert response.status_code == 200
|
|
|
|
# Step 5: Page should render
|
|
response = await async_client.get("/logs")
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestAgentMonitoringWorkflow:
|
|
"""Test agent monitoring workflows."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_view_agent_status(self, async_client):
|
|
"""
|
|
Workflow: View agents -> Check history.
|
|
"""
|
|
# Step 1: Get all agents status
|
|
response = await async_client.get("/api/agents")
|
|
assert response.status_code == 200
|
|
agents = response.json().get("agents", [])
|
|
|
|
# Step 2: Get history for each agent type
|
|
for agent_name in ["research", "risk", "execution", "reflection"]:
|
|
response = await async_client.get(f"/api/agents/{agent_name}/history")
|
|
assert response.status_code == 200
|
|
|
|
# Step 3: Page should render
|
|
response = await async_client.get("/agents")
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestNavigationWorkflow:
|
|
"""Test navigation between pages."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_all_pages_accessible(self, async_client):
|
|
"""
|
|
Verify all main pages are accessible.
|
|
"""
|
|
pages = [
|
|
"/",
|
|
"/trading",
|
|
"/positions",
|
|
"/markets",
|
|
"/wallet-analysis",
|
|
"/agents",
|
|
"/risk",
|
|
"/config",
|
|
"/logs",
|
|
"/help",
|
|
]
|
|
|
|
for page in pages:
|
|
response = await async_client.get(page)
|
|
assert response.status_code == 200, f"Failed to load {page}"
|
|
assert "text/html" in response.headers.get("content-type", "")
|
|
|
|
|
|
class TestDataConsistency:
|
|
"""Test data consistency across endpoints."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_status_consistency(self, async_client):
|
|
"""
|
|
Verify trading status is consistent across endpoints.
|
|
"""
|
|
# Get status from trading endpoint
|
|
trading_response = await async_client.get("/api/trading/status")
|
|
assert trading_response.status_code == 200
|
|
trading_status = trading_response.json()
|
|
|
|
# Get status from health endpoint
|
|
health_response = await async_client.get("/api/system/health")
|
|
assert health_response.status_code == 200
|
|
|
|
# Both should reflect consistent state
|
|
# (This test documents expected behavior)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_risk_status_matches_config(self, async_client):
|
|
"""
|
|
Verify risk status reflects configuration.
|
|
"""
|
|
# Get risk status
|
|
status_response = await async_client.get("/api/risk/status")
|
|
assert status_response.status_code == 200
|
|
|
|
# Get config
|
|
config_response = await async_client.get("/api/config")
|
|
assert config_response.status_code == 200
|
|
|
|
|
|
class TestErrorRecovery:
|
|
"""Test system behavior after errors."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_system_stable_after_invalid_requests(self, async_client):
|
|
"""
|
|
System should remain stable after handling invalid requests.
|
|
"""
|
|
# Send various invalid requests
|
|
await async_client.post("/api/trading/start", json={"invalid": "data"})
|
|
await async_client.post("/api/wallet/analyze", json={"address": "bad"})
|
|
await async_client.get("/api/nonexistent")
|
|
|
|
# System should still be responsive
|
|
response = await async_client.get("/api/system/health")
|
|
assert response.status_code == 200
|
|
assert response.json().get("status") == "healthy"
|
|
|
|
# All pages should still render
|
|
response = await async_client.get("/")
|
|
assert response.status_code == 200
|