Files
DinQuant/backend_api_python/app/data_sources/forex.py
T
TIANHE f43312a858 creat
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
2025-12-29 03:06:49 +08:00

191 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
外汇数据源
使用 Tiingo 获取外汇数据
"""
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
import time
import requests
from app.data_sources.base import BaseDataSource, TIMEFRAME_SECONDS
from app.utils.logger import get_logger
from app.config import TiingoConfig, APIKeys
logger = get_logger(__name__)
class ForexDataSource(BaseDataSource):
"""外汇数据源 (Tiingo)"""
name = "Forex/Tiingo"
# Tiingo resampleFreq 映射
# Tiingo 支持: 1min, 5min, 15min, 30min, 1hour, 4hour, 1day 等
TIMEFRAME_MAP = {
'1m': '1min',
'5m': '5min',
'15m': '15min',
'30m': '30min',
'1H': '1hour',
'4H': '4hour',
'1D': '1day',
'1W': '1week',
'1M': '1month'
}
# 外汇对映射 (Tiingo 使用标准 ticker,如 eurusd, audusd)
# 大写也可以,Tiingo 通常不区分大小写,但建议统一
SYMBOL_MAP = {
# 贵金属 (Tiingo 不一定支持所有 OANDA 格式的贵金属,通常是 XAUUSD)
'XAUUSD': 'xauusd',
'XAGUSD': 'xagusd',
# 主要货币对
'EURUSD': 'eurusd',
'GBPUSD': 'gbpusd',
'USDJPY': 'usdjpy',
'AUDUSD': 'audusd',
'USDCAD': 'usdcad',
'USDCHF': 'usdchf',
'NZDUSD': 'nzdusd',
}
def __init__(self):
self.base_url = TiingoConfig.BASE_URL
if not APIKeys.TIINGO_API_KEY:
logger.warning("Tiingo API key is not configured; FX data will be unavailable")
def _get_timeframe_seconds(self, timeframe: str) -> int:
"""获取时间周期对应的秒数"""
return TIMEFRAME_SECONDS.get(timeframe, 86400)
def get_kline(
self,
symbol: str,
timeframe: str,
limit: int,
before_time: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
获取外汇K线数据
Args:
symbol: 外汇对代码(如 XAUUSD, EURUSD
timeframe: 时间周期
limit: 数据条数
before_time: 结束时间戳
"""
# 动态获取 API Key
api_key = APIKeys.TIINGO_API_KEY
if not api_key:
logger.error("Tiingo API key is not configured")
return []
try:
# 1. 解析 Symbol
tiingo_symbol = self.SYMBOL_MAP.get(symbol)
if not tiingo_symbol:
# 尝试智能转换: EURUSD -> eurusd
tiingo_symbol = symbol.lower()
# 2. 解析 Resolution (resampleFreq)
resample_freq = self.TIMEFRAME_MAP.get(timeframe)
if not resample_freq:
logger.warning(f"Tiingo does not support timeframe: {timeframe}")
return []
# 3. 计算时间范围
if before_time:
end_dt = datetime.fromtimestamp(before_time)
else:
end_dt = datetime.now()
# 根据周期和数量计算开始时间
tf_seconds = self._get_timeframe_seconds(timeframe)
# 多取一些缓冲时间
start_dt = end_dt - timedelta(seconds=limit * tf_seconds * 2)
# 格式化日期为 YYYY-MM-DD (Tiingo 支持该格式)
start_date_str = start_dt.strftime('%Y-%m-%d')
end_date_str = end_dt.strftime('%Y-%m-%d')
# 4. API 请求
# URL: https://api.tiingo.com/tiingo/fx/{ticker}/prices
url = f"{self.base_url}/fx/{tiingo_symbol}/prices"
params = {
'startDate': start_date_str,
'endDate': end_date_str,
'resampleFreq': resample_freq,
'token': api_key,
'format': 'json'
}
# logger.info(f"Tiingo Request: {url} params={params}")
response = requests.get(url, params=params, timeout=TiingoConfig.TIMEOUT)
if response.status_code == 403: # 具体的权限错误
logger.error("Tiingo API permission error (403): check whether your API key is valid and has access to this dataset.")
return []
response.raise_for_status()
data = response.json()
# 5. 处理响应
# Tiingo returns a list of dicts:
# [
# {
# "date": "2023-01-01T00:00:00.000Z",
# "ticker": "eurusd",
# "open": 1.07,
# "high": 1.08,
# "low": 1.06,
# "close": 1.07
# "mid": ... (optional, depends on settings, usually OHLC are bid or mid)
# }, ...
# ]
# Note: Tiingo FX prices objects keys: date, open, high, low, close.
if not isinstance(data, list):
logger.warning(f"Tiingo response is not a list: {data}")
return []
klines = []
for item in data:
# 解析时间: "2023-01-01T00:00:00.000Z"
dt_str = item.get('date')
# 简化处理,Tiingo 返回的是 UTC 时间 ISO 格式
# datetime.fromisoformat 在 Py3.7+ 支持,但要注意 Z 的处理
# 这里简单处理一下 Z
if dt_str.endswith('Z'):
dt_str = dt_str[:-1]
dt = datetime.fromisoformat(dt_str)
ts = int(dt.timestamp())
klines.append({
'time': ts,
'open': float(item.get('open')),
'high': float(item.get('high')),
'low': float(item.get('low')),
'close': float(item.get('close')),
'volume': 0.0 # Tiingo FX 通常没有 volume
})
# 按时间排序
klines.sort(key=lambda x: x['time'])
# 过滤
if len(klines) > limit:
klines = klines[-limit:]
# logger.info(f"获取到 {len(klines)} 条 Tiingo 外汇数据")
return klines
except requests.exceptions.RequestException as e:
logger.error(f"Tiingo API request failed: {e}")
return []
except Exception as e:
logger.error(f"Failed to process Tiingo data: {e}")
return []