- Add advanced metrics dashboard with trade analytics - Add new trading components (EntryTypeAnalysis, MultiDayPositionTracker, NewsEventTracker, etc.) - Add strategy mode selector and trend confirmation - Add risk automation panel and slippage correlation analysis - Add daily trading plan enhancements with modal components - Add custom hooks (useApi, useLocalStorage, useAdvancedTradeMetrics) - Add broker service integration and trading API - Add test setup and vitest configuration - Include parquet data files for live market data - Add comprehensive documentation in docs/ folder
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
from datetime import datetime, timezone
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from app.services.metals.bullionvault_service import BullionVaultService
|
|
|
|
|
|
def _csv_response(rows: list[str]) -> str:
|
|
header = (
|
|
'"Date","High (kg)","Low (kg)","Close (kg)",,"High (troy oz)","Low (troy oz)","Close (troy oz)",\n'
|
|
)
|
|
return header + "".join(f"{row}\n" for row in rows)
|
|
|
|
|
|
def _mock_client(handler):
|
|
transport = httpx.MockTransport(handler)
|
|
return httpx.AsyncClient(transport=transport)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_gold_price_parses_csv() -> None:
|
|
csv = _csv_response(
|
|
[
|
|
'"12:00:00 23-Nov-2025",130702.99,130600.11,130650.10,,4065.32,4040.32,4050.55,',
|
|
'"11:50:00 23-Nov-2025",130600.99,130500.11,130550.10,,4055.32,4030.32,4040.55,',
|
|
]
|
|
)
|
|
|
|
async def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, text=csv)
|
|
|
|
async with _mock_client(handler) as client:
|
|
service = BullionVaultService(client=client, base_url="https://chart-data.bullionvault.com")
|
|
result = await service.get_current_gold_price("usd")
|
|
|
|
assert result["currency"] == "USD"
|
|
assert result["price"] == pytest.approx(4050.55, rel=0.001)
|
|
assert result["price_kg"] == pytest.approx(130650.10, rel=0.001)
|
|
assert result["data_points"] == 2
|
|
assert datetime.fromisoformat(result["timestamp"]).tzinfo == timezone.utc
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_gold_price_raises_on_empty_data() -> None:
|
|
async def handler(_: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, text='"Date",\n')
|
|
|
|
async with _mock_client(handler) as client:
|
|
service = BullionVaultService(client=client)
|
|
|
|
with pytest.raises(ValueError, match="No price data available"):
|
|
await service.get_current_gold_price()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_gold_history_applies_limit() -> None:
|
|
csv = _csv_response(
|
|
[
|
|
'"12:00:00 23-Nov-2025",130702.99,130600.11,130650.10,,4065.32,4040.32,4050.55,',
|
|
'"11:50:00 23-Nov-2025",130600.99,130500.11,130550.10,,4055.32,4030.32,4040.55,',
|
|
'"11:40:00 23-Nov-2025",130500.99,130400.11,130450.10,,4045.32,4020.32,4030.55,',
|
|
]
|
|
)
|
|
|
|
async def handler(_: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, text=csv)
|
|
|
|
async with _mock_client(handler) as client:
|
|
service = BullionVaultService(client=client)
|
|
history = await service.get_gold_history(limit=2)
|
|
|
|
assert len(history) == 2
|
|
assert history[0]["close"] == pytest.approx(4050.55, rel=0.001)
|
|
assert history[1]["close"] == pytest.approx(4040.55, rel=0.001)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_multi_currency_prices_skips_failures() -> None:
|
|
csv = _csv_response(
|
|
[
|
|
'"12:00:00 23-Nov-2025",130702.99,130600.11,130650.10,,4065.32,4040.32,4050.55,',
|
|
]
|
|
)
|
|
|
|
retry_counts: dict[str, int] = {}
|
|
|
|
async def handler(request: httpx.Request) -> httpx.Response:
|
|
currency = request.url.path.split("/")[4]
|
|
retry_counts[currency] = retry_counts.get(currency, 0) + 1
|
|
|
|
if currency == "GBP":
|
|
return httpx.Response(500, text="error")
|
|
|
|
return httpx.Response(200, text=csv)
|
|
|
|
async with _mock_client(handler) as client:
|
|
service = BullionVaultService(client=client, max_retries=1)
|
|
prices = await service.get_multi_currency_prices()
|
|
|
|
# Only currencies served by successful responses should be included
|
|
assert "USD" in prices
|
|
assert "EUR" in prices
|
|
assert "GBP" not in prices
|
|
assert retry_counts["GBP"] == 1
|