You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
7.2 KiB

"""
数据同步服务
定时从 amazingData 同步数据到 TimescaleDB
"""
import logging
from datetime import datetime, date, timedelta
from typing import List, Optional
import asyncio
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.db.init_db import TimescaleSessionLocal
from app.services.amazing_data_service import amazing_data_service
logger = logging.getLogger(__name__)
class DataSyncService:
"""数据同步服务"""
# 默认同步的品种列表
DEFAULT_SYMBOLS = [
"IF2406", # 沪深 300 股指期货
"IC2406", # 中证 500 股指期货
"IH2406", # 上证 50 股指期货
"IM2406", # 中证 1000 股指期货
]
# 默认同步的周期
DEFAULT_PERIODS = ["1m", "5m", "15m", "30m", "1h", "1d"]
@staticmethod
def sync_kline_data(
symbol: str,
period: str,
start_date: Optional[str] = None,
end_date: Optional[str] = None
) -> int:
"""
同步 K 线数据到 TimescaleDB
Args:
symbol: 品种代码
period: 周期
start_date: 开始日期 (YYYY-MM-DD)默认昨天
end_date: 结束日期 (YYYY-MM-DD)默认今天
Returns:
int: 同步的记录数
"""
try:
# 默认日期范围
if not end_date:
end_date = datetime.now().strftime("%Y-%m-%d")
if not start_date:
start_date = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
logger.info(f"Syncing kline data: {symbol} {period} from {start_date} to {end_date}")
# 从 amazingData 获取数据
kline_data = amazing_data_service.get_kline_data(
symbol=symbol,
period=period,
start_date=start_date,
end_date=end_date
)
if not kline_data:
logger.warning(f"No data to sync for {symbol} {period}")
return 0
# 插入到 TimescaleDB
count = 0
with TimescaleSessionLocal() as db:
for record in kline_data:
try:
query = text("""
INSERT INTO kline_data (time, symbol, period, open, high, low, close, volume, amount, open_interest)
VALUES (:time, :symbol, :period, :open, :high, :low, :close, :volume, :amount, :open_interest)
ON CONFLICT (time, symbol, period) DO UPDATE SET
open = EXCLUDED.open,
high = EXCLUDED.high,
low = EXCLUDED.low,
close = EXCLUDED.close,
volume = EXCLUDED.volume,
amount = EXCLUDED.amount,
open_interest = EXCLUDED.open_interest
""")
# 处理时间字段
time_val = record.get('time')
if isinstance(time_val, str):
time_val = datetime.fromisoformat(time_val.replace('Z', '+00:00'))
db.execute(
query,
{
'time': time_val,
'symbol': symbol,
'period': period,
'open': float(record.get('open', 0)),
'high': float(record.get('high', 0)),
'low': float(record.get('low', 0)),
'close': float(record.get('close', 0)),
'volume': float(record.get('volume', 0)),
'amount': float(record.get('amount', 0)),
'open_interest': float(record.get('open_interest', 0))
}
)
count += 1
except Exception as e:
logger.error(f"Error inserting record: {e}")
continue
db.commit()
logger.info(f"Synced {count} records for {symbol} {period}")
return count
except Exception as e:
logger.error(f"Error syncing data for {symbol} {period}: {e}")
raise
@staticmethod
async def sync_all_symbols(
symbols: Optional[List[str]] = None,
periods: Optional[List[str]] = None
) -> dict:
"""
异步同步所有品种数据
Args:
symbols: 品种列表默认使用 DEFAULT_SYMBOLS
periods: 周期列表默认使用 DEFAULT_PERIODS
Returns:
dict: 同步结果统计
"""
symbols = symbols or DataSyncService.DEFAULT_SYMBOLS
periods = periods or DataSyncService.DEFAULT_PERIODS
results = {}
total_count = 0
# 确保连接
if not amazing_data_service.ensure_connected():
logger.error("Failed to connect to amazingData")
return {'error': 'Connection failed', 'total': 0}
for symbol in symbols:
for period in periods:
try:
count = DataSyncService.sync_kline_data(symbol, period)
results[f"{symbol}_{period}"] = count
total_count += count
# 避免请求过快
await asyncio.sleep(0.5)
except Exception as e:
logger.error(f"Failed to sync {symbol} {period}: {e}")
results[f"{symbol}_{period}"] = 0
return {
'success': True,
'total_records': total_count,
'details': results
}
@staticmethod
def sync_realtime_quotes(symbols: List[str]) -> int:
"""
同步实时行情到 Redis
Args:
symbols: 品种列表
Returns:
int: 同步的记录数
"""
try:
quotes = amazing_data_service.get_realtime_quotes(symbols)
if not quotes:
return 0
# 这里可以写入 Redis 缓存
# 具体实现取决于 Redis 服务的设计
logger.info(f"Synced {len(quotes)} realtime quotes")
return len(quotes)
except Exception as e:
logger.error(f"Error syncing realtime quotes: {e}")
raise
# 便捷函数
def sync_kline(symbol: str, period: str, start_date: str = None, end_date: str = None) -> int:
"""同步 K 线数据"""
return DataSyncService.sync_kline_data(symbol, period, start_date, end_date)
async def sync_all() -> dict:
"""同步所有数据"""
return await DataSyncService.sync_all_symbols()