91 lines
4.2 KiB
Python
91 lines
4.2 KiB
Python
|
|
import time
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
import logging
|
|||
|
|
from typing import Optional
|
|||
|
|
import pandas as pd
|
|||
|
|
import okx.MarketData as Market
|
|||
|
|
|
|||
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
|
|||
|
|
|
|||
|
|
class DataMonitor:
|
|||
|
|
def __init__(self,
|
|||
|
|
api_key: str,
|
|||
|
|
secret_key: str,
|
|||
|
|
passphrase: str,
|
|||
|
|
sandbox: bool = True):
|
|||
|
|
flag = "1" if sandbox else "0" # 0:实盘环境 1:沙盒环境
|
|||
|
|
self.market_api = Market.MarketAPI(
|
|||
|
|
api_key=api_key, api_secret_key=secret_key, passphrase=passphrase,
|
|||
|
|
flag=flag
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def get_historical_kline_data(self, symbol: str = None, start: str = None, bar: str = '1m', limit: int = 100, end_time: int = None) -> Optional[pd.DataFrame]:
|
|||
|
|
"""
|
|||
|
|
获取历史K线数据,支持start为北京时间字符串(%Y-%m-%d %H:%M:%S)或UTC毫秒级时间戳
|
|||
|
|
:param symbol: 交易对
|
|||
|
|
:param start: 起始时间(北京时间字符串或UTC毫秒级时间戳)
|
|||
|
|
:param bar: K线周期
|
|||
|
|
:param limit: 每次请求数量
|
|||
|
|
:param end_time: 结束时间(毫秒级timestamp),默认当前时间
|
|||
|
|
:return: pd.DataFrame
|
|||
|
|
"""
|
|||
|
|
from core.utils import datetime_to_timestamp
|
|||
|
|
if symbol is None:
|
|||
|
|
symbol = "XCH-USDT"
|
|||
|
|
if end_time is None:
|
|||
|
|
end_time = int(time.time() * 1000) # 当前时间(毫秒)
|
|||
|
|
# 处理start参数
|
|||
|
|
if start is None:
|
|||
|
|
# 默认两个月前
|
|||
|
|
two_months_ago = datetime.now() - timedelta(days=60)
|
|||
|
|
start_time = int(two_months_ago.timestamp() * 1000)
|
|||
|
|
else:
|
|||
|
|
try:
|
|||
|
|
# 判断是否为纯数字(UTC毫秒级timestamp)
|
|||
|
|
if start.isdigit():
|
|||
|
|
start_time = int(start)
|
|||
|
|
else:
|
|||
|
|
# 按北京时间字符串处理,转换为毫秒级timestamp
|
|||
|
|
start_time = datetime_to_timestamp(start)
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.error(f"start参数解析失败: {e}")
|
|||
|
|
return None
|
|||
|
|
columns = ["timestamp", "open", "high", "low", "close", "volume", "volCcy", "volCCyQuote", "confirm"]
|
|||
|
|
all_data = []
|
|||
|
|
while start_time < end_time:
|
|||
|
|
try:
|
|||
|
|
# after,真实逻辑是获得指定时间之前的数据 !!!
|
|||
|
|
response = self.market_api.get_history_candlesticks(
|
|||
|
|
instId=symbol,
|
|||
|
|
after=end_time, # 获取指定时间之前的数据,
|
|||
|
|
bar=bar,
|
|||
|
|
limit=str(limit)
|
|||
|
|
)
|
|||
|
|
if response["code"] != "0" or not response["data"]:
|
|||
|
|
logging.warning(f"请求失败或无数据: {response.get('msg', 'No message')}")
|
|||
|
|
break
|
|||
|
|
candles = response["data"]
|
|||
|
|
all_data.extend(candles)
|
|||
|
|
# 更新 end_time 为本次请求中最早的时间戳
|
|||
|
|
end_time = int(candles[-1][0])
|
|||
|
|
logging.info(f"已获取 {len(candles)} 条数据,最早时间: {pd.to_datetime(end_time, unit='ms', utc=True).tz_convert('Asia/Shanghai')}")
|
|||
|
|
end_time -= 1 # 减 1 毫秒以避免重复
|
|||
|
|
time.sleep(0.2)
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.error(f"请求出错: {e}")
|
|||
|
|
break
|
|||
|
|
if all_data:
|
|||
|
|
df = pd.DataFrame(all_data, columns=columns)
|
|||
|
|
df = df[df['confirm'] == '1']
|
|||
|
|
for col in ['open', 'high', 'low', 'close', 'volume']:
|
|||
|
|
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|||
|
|
dt_series = pd.to_datetime(df['timestamp'].astype(int), unit='ms', utc=True, errors='coerce').dt.tz_convert('Asia/Shanghai')
|
|||
|
|
df['date_time'] = dt_series.dt.strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
df = df[['timestamp', 'date_time', 'open', 'high', 'low', 'close', 'volume']]
|
|||
|
|
df.sort_values('timestamp', inplace=True)
|
|||
|
|
df.reset_index(drop=True, inplace=True)
|
|||
|
|
logging.info(f"总计获取 {len(df)} 条 K 线数据(仅confirm=1)")
|
|||
|
|
return df
|
|||
|
|
else:
|
|||
|
|
logging.warning("未获取到数据")
|
|||
|
|
return None
|