feat: Add Phase 4 advanced metrics and components
- 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
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
BullionVault Gold Price Service
|
||||
Fetches real-time gold prices from BullionVault's chart data API
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BullionVaultService:
|
||||
"""
|
||||
Service to fetch gold prices from BullionVault
|
||||
BullionVault provides accurate, real-time precious metals prices
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = httpx.AsyncClient(timeout=15.0)
|
||||
self.base_url = "https://www.bullionvault.com"
|
||||
# BullionVault chart data endpoint
|
||||
self.chart_data_url = f"{self.base_url}/chart/chart-data.json"
|
||||
|
||||
async def get_current_gold_price(self, currency: str = "USD") -> Dict[str, Any]:
|
||||
"""
|
||||
Get current gold spot price from BullionVault
|
||||
|
||||
Args:
|
||||
currency: Currency code (USD, GBP, EUR, JPY, AUD, CAD, CHF)
|
||||
|
||||
Returns:
|
||||
Dict with price, high, low, change, timestamp, etc.
|
||||
"""
|
||||
try:
|
||||
# Fetch latest gold price data
|
||||
params = {
|
||||
"bullion": "gold",
|
||||
"currency": currency.upper(),
|
||||
"timeframe": "1d", # 1 day for recent data
|
||||
"chartType": "line"
|
||||
}
|
||||
|
||||
response = await self.client.get(self.chart_data_url, params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if not data or "prices" not in data:
|
||||
raise ValueError("Invalid response from BullionVault")
|
||||
|
||||
prices = data["prices"]
|
||||
if not prices:
|
||||
raise ValueError("No price data available")
|
||||
|
||||
# Get latest price point
|
||||
latest = prices[-1]
|
||||
|
||||
# Calculate daily statistics
|
||||
daily_prices = [p[1] for p in prices if p[1] is not None]
|
||||
|
||||
if not daily_prices:
|
||||
raise ValueError("No valid price points")
|
||||
|
||||
current_price = latest[1] # Price per ounce
|
||||
daily_high = max(daily_prices)
|
||||
daily_low = min(daily_prices)
|
||||
|
||||
# Calculate change from first price of day
|
||||
first_price = prices[0][1]
|
||||
change = current_price - first_price
|
||||
change_percent = (change / first_price * 100) if first_price else 0.0
|
||||
|
||||
# Convert timestamp (BullionVault uses milliseconds)
|
||||
timestamp_ms = latest[0]
|
||||
timestamp = datetime.fromtimestamp(timestamp_ms / 1000.0)
|
||||
|
||||
result = {
|
||||
"price": round(current_price, 2),
|
||||
"open": round(first_price, 2),
|
||||
"high": round(daily_high, 2),
|
||||
"low": round(daily_low, 2),
|
||||
"previous_close": round(first_price, 2),
|
||||
"change": round(change, 2),
|
||||
"change_percent": round(change_percent, 4),
|
||||
"currency": currency.upper(),
|
||||
"unit": "per troy oz",
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"timestamp_ms": timestamp_ms,
|
||||
"source": "BullionVault",
|
||||
"trading_day": timestamp.strftime("%Y-%m-%d"),
|
||||
"data_points": len(prices)
|
||||
}
|
||||
|
||||
logger.info(f"✅ BullionVault gold price: {currency} ${current_price:.2f}/oz")
|
||||
return result
|
||||
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"❌ BullionVault HTTP error: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ BullionVault price fetch failed: {e}")
|
||||
raise
|
||||
|
||||
async def get_gold_history(
|
||||
self,
|
||||
currency: str = "USD",
|
||||
timeframe: str = "1d",
|
||||
limit: Optional[int] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get historical gold price data from BullionVault
|
||||
|
||||
Args:
|
||||
currency: Currency code
|
||||
timeframe: Time range (10m, 1h, 6h, 1d, 1w, 1m, 1q, 1y, 5y, 20y)
|
||||
limit: Maximum number of data points to return
|
||||
|
||||
Returns:
|
||||
List of OHLC data points
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"bullion": "gold",
|
||||
"currency": currency.upper(),
|
||||
"timeframe": timeframe,
|
||||
"chartType": "hlc" # High-Low-Close for OHLC data
|
||||
}
|
||||
|
||||
response = await self.client.get(self.chart_data_url, params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if not data or "prices" not in data:
|
||||
return []
|
||||
|
||||
prices = data["prices"]
|
||||
|
||||
# Apply limit if specified
|
||||
if limit and len(prices) > limit:
|
||||
prices = prices[-limit:]
|
||||
|
||||
# Convert to OHLCV format
|
||||
result = []
|
||||
for point in prices:
|
||||
if len(point) >= 4: # [timestamp, open, high, low, close]
|
||||
timestamp_ms = point[0]
|
||||
result.append({
|
||||
"timestamp": datetime.fromtimestamp(timestamp_ms / 1000.0).isoformat(),
|
||||
"time": int(timestamp_ms / 1000),
|
||||
"open": float(point[1]) if point[1] is not None else 0.0,
|
||||
"high": float(point[2]) if point[2] is not None else 0.0,
|
||||
"low": float(point[3]) if point[3] is not None else 0.0,
|
||||
"close": float(point[4]) if len(point) > 4 and point[4] is not None else float(point[1]),
|
||||
"volume": 0, # BullionVault doesn't provide volume
|
||||
})
|
||||
|
||||
logger.info(f"✅ BullionVault history: {len(result)} points for {timeframe}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ BullionVault history fetch failed: {e}")
|
||||
return []
|
||||
|
||||
async def get_multi_currency_prices(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Get current gold prices in multiple currencies
|
||||
|
||||
Returns:
|
||||
Dict mapping currency codes to price data
|
||||
"""
|
||||
currencies = ["USD", "GBP", "EUR", "JPY", "AUD", "CAD", "CHF"]
|
||||
tasks = [self.get_current_gold_price(curr) for curr in currencies]
|
||||
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
prices = {}
|
||||
for curr, result in zip(currencies, results):
|
||||
if isinstance(result, dict):
|
||||
prices[curr] = result
|
||||
else:
|
||||
logger.warning(f"Failed to fetch {curr} price: {result}")
|
||||
|
||||
return prices
|
||||
|
||||
async def close(self):
|
||||
"""Close HTTP client"""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
# Global instance
|
||||
bullionvault_service = BullionVaultService()
|
||||
|
||||
|
||||
# Convenience functions
|
||||
async def get_bullionvault_gold_price(currency: str = "USD") -> Dict[str, Any]:
|
||||
"""Get current gold price from BullionVault"""
|
||||
return await bullionvault_service.get_current_gold_price(currency)
|
||||
|
||||
|
||||
async def get_bullionvault_history(
|
||||
currency: str = "USD",
|
||||
timeframe: str = "1d",
|
||||
limit: Optional[int] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Get historical gold prices from BullionVault"""
|
||||
return await bullionvault_service.get_gold_history(currency, timeframe, limit)
|
||||
Reference in New Issue
Block a user