From c670be6f7dd0955adba6e35bdbb3c39643f10ad1 Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Thu, 24 Jul 2025 18:23:00 +0800 Subject: [PATCH] support get market data --- config.py | 34 ++++++++++++-- core/base.py | 29 +++++++++--- core/data_monitor.py | 91 ++++++++++++++++++++++++++++++++++++ core/utils.py | 20 ++++++++ monitor_main.py | 33 +++++++++++++ plan.md | 29 ++++++++++++ play.py | 8 ++-- biz_main.py => trade_main.py | 5 +- wechat.py | 6 +++ 9 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 core/data_monitor.py create mode 100644 core/utils.py create mode 100644 monitor_main.py create mode 100644 plan.md rename biz_main.py => trade_main.py (94%) create mode 100644 wechat.py diff --git a/config.py b/config.py index b429055..3774036 100644 --- a/config.py +++ b/config.py @@ -4,16 +4,24 @@ # API_KEY = "7286d434-225b-401f-b3af-fd595e15d23f" # SECRET_KEY = "80B95C5757F9208F70282A85C9DDBC86" # PASSPHRASE = "Bengbu_2001" +# SANDBOX = False + +# 实盘读取API密钥配置 +API_KEY = "a73f9096-8e76-49ff-947c-a4f4edf657ec" +SECRET_KEY = "F7AD69272FBF7C44E69CC110D2EDDB7A" +PASSPHRASE = "Bengbu!2001" +SANDBOX = False + # 模拟盘API密钥配置 -API_KEY = "f309e789-3497-4ed3-896f-d18bdc4d9817" -SECRET_KEY = "9152809391B110E2E647FDE12A37E96D" -PASSPHRASE = "Bengbu@2001" +# API_KEY = "f309e789-3497-4ed3-896f-d18bdc4d9817" +# SECRET_KEY = "9152809391B110E2E647FDE12A37E96D" +# PASSPHRASE = "Bengbu@2001" +# SANDBOX = True # 交易配置 TRADING_CONFIG = { "symbol": "BTC-USDT", # 交易对 "position_size": 0.001, # 每次交易数量(BTC) - "sandbox": True, # 是否使用沙盒环境(建议先用沙盒测试) # 策略参数 "sma_short_period": 5, # 短期移动平均线周期 @@ -38,4 +46,20 @@ TIME_CONFIG = { "strategy_interval": 30, # 策略执行间隔(秒) "kline_interval": "5m", # K线数据间隔 "kline_limit": 100, # K线数据条数 -} \ No newline at end of file +} + +MONITOR_CONFIG = { + "volume_monitor":{ + "symbols": ["XCH-USDT"], + "intervals": ["5m", "15m", "1H", "4H", "1D"], + "initial_date": "2025-07-01 00:00:00" + }, + "price_monitor":{ + "symbols": ["XCH-USDT"], + "intervals": [ + {"interval": "5m", "threshold": 0.025}, + {"interval": "15m", "threshold": 0.5}, + {"interval": "1H", "threshold": 0.1} + ] + } +} \ No newline at end of file diff --git a/core/base.py b/core/base.py index faf0fe6..933de49 100644 --- a/core/base.py +++ b/core/base.py @@ -101,14 +101,31 @@ class QuantTrader: logging.error(f"获取价格异常: {e}") return None - def get_kline_data(self, bar: str = '1m', limit: int = 100) -> Optional[pd.DataFrame]: + def get_kline_data(self, symbol: str = None, after: str = None, before: str = None, bar: str = '1m', limit: int = 100) -> Optional[pd.DataFrame]: """获取K线数据""" + if symbol is None: + symbol = self.symbol try: - result = self.market_api.get_candlesticks( - instId=self.symbol, - bar=bar, - limit=str(limit) - ) + if after is None and before is None: + result = self.market_api.get_candlesticks( + instId=symbol, + bar=bar, + limit=str(limit) + ) + else: + if after is None: + after = '' + if before is None: + before = '' + if limit == 0: + limit = '' + result = self.market_api.get_candlesticks( + instId=symbol, + after=after, + before=before, + bar=bar, + limit=str(limit), + ) if result.get('code') == '0': data = result.get('data', []) if not data: diff --git a/core/data_monitor.py b/core/data_monitor.py new file mode 100644 index 0000000..fba9eea --- /dev/null +++ b/core/data_monitor.py @@ -0,0 +1,91 @@ +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 \ No newline at end of file diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..d1c9ecc --- /dev/null +++ b/core/utils.py @@ -0,0 +1,20 @@ +from datetime import datetime, timezone, timedelta + +def datetime_to_timestamp(date_str: str) -> int: + """ + 将日期字符串(如 '2023-01-01 12:00:00')直接转换为毫秒级时间戳 + :param date_str: 形如 '2023-01-01 12:00:00' 的日期时间字符串 + :return: 毫秒级时间戳 + """ + dt = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') + return int(dt.timestamp() * 1000) + +def timestamp_to_datetime(timestamp: int) -> str: + """ + 将时间戳转换为日期字符串 + 请考虑日期字符串位于北京时区 + :param timestamp: 以毫秒为单位的时间戳 + :return: 形如 '2023-01-01 12:00:00' 的日期时间字符串 + """ + dt = datetime.fromtimestamp(timestamp / 1000, timezone(timedelta(hours=8))) + return dt.strftime('%Y-%m-%d %H:%M:%S') \ No newline at end of file diff --git a/monitor_main.py b/monitor_main.py new file mode 100644 index 0000000..b565288 --- /dev/null +++ b/monitor_main.py @@ -0,0 +1,33 @@ +import logging +from time import sleep +from core.data_monitor import DataMonitor +from config import API_KEY, SECRET_KEY, PASSPHRASE, SANDBOX, MONITOR_CONFIG + +logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') + + +class MonitorMain: + def __init__(self): + self.data_monitor = DataMonitor( + api_key=API_KEY, + secret_key=SECRET_KEY, + passphrase=PASSPHRASE, + sandbox=SANDBOX, + ) + self.symbols = MONITOR_CONFIG.get("volume_monitor", {}).get("symbols", ["XCH-USDT"]) + self.intervals = MONITOR_CONFIG.get("volume_monitor", {}).get("intervals", ["5m", "15m", "1H", "4H", "1D"]) + self.initial_date = MONITOR_CONFIG.get("volume_monitor", {}).get("initial_date", "2025-07-01 00:00:00") + + def initial_data(self): + for symbol in self.symbols: + for interval in self.intervals: + data = self.data_monitor.get_historical_kline_data(symbol=symbol, + start=self.initial_date, + bar=interval) + + +if __name__ == "__main__": + monitor_main = MonitorMain() + monitor_main.initial_data() + + diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..9dc5f4f --- /dev/null +++ b/plan.md @@ -0,0 +1,29 @@ +# 量化工作计划 +## 准备阶段 +### API熟悉过程 +1. 掌握API具体细节 +- 虚拟货币价格查询 +- 账户余额查询 +- 现货交易买卖 +- 永续合约买卖 +- 设置杠杆 +- 获得交易数据 +### 实验阶段 +1. 基于模拟盘实验 +2. 设置支持永续合约权限 +3. 完成现货买卖测试 +4. 完成永续合约买平测试 +5. 设置业务流程 +- 开空单 +- 卖现货 +- 平空单 +- 统计收益率 +6. 进阶实验 +- 获取对应货币技术指标 +- 获取1分钟线KDJ, RSI,均线等技术形态 +- 如果处于超买状态,以超买形态高点为参照物 +- 之后连续三个周期,价格低于形态高点价格 +- 此时方便做空,利于进行业务流程操作 +6. 反复验证策略安全与完整性 +### 实战阶段 + diff --git a/play.py b/play.py index 6c3268e..ec306b8 100644 --- a/play.py +++ b/play.py @@ -9,7 +9,7 @@ def main() -> None: logging.info("=== 比特币量化交易系统 ===") # 导入配置 try: - from config import API_KEY, SECRET_KEY, PASSPHRASE, TRADING_CONFIG, TIME_CONFIG + from config import API_KEY, SECRET_KEY, PASSPHRASE, SANDBOX, TRADING_CONFIG, TIME_CONFIG except ImportError: logging.error("找不到config.py文件,请确保配置文件存在") return @@ -18,18 +18,18 @@ def main() -> None: logging.error("请先在config.py中配置你的OKX API密钥!\n1. 登录OKX官网\n2. 进入API管理页面\n3. 创建API Key、Secret Key和Passphrase\n4. 将密钥填入config.py文件中的相应位置") return # 创建交易器实例 - sandbox = TRADING_CONFIG.get("sandbox", True) + # sandbox = TRADING_CONFIG.get("sandbox", True) symbol = TRADING_CONFIG.get("symbol", "BTC-USDT") position_size = TRADING_CONFIG.get("position_size", 0.001) trader = QuantTrader( API_KEY, SECRET_KEY, PASSPHRASE, - sandbox=sandbox, + sandbox=SANDBOX, symbol=symbol, position_size=position_size ) strategy = QuantStrategy( API_KEY, SECRET_KEY, PASSPHRASE, - sandbox=sandbox, + sandbox=SANDBOX, symbol=symbol, position_size=position_size ) diff --git a/biz_main.py b/trade_main.py similarity index 94% rename from biz_main.py rename to trade_main.py index 1376f46..b1b654e 100644 --- a/biz_main.py +++ b/trade_main.py @@ -2,7 +2,7 @@ import logging from time import sleep from core.base import QuantTrader from core.strategy import QuantStrategy -from config import API_KEY, SECRET_KEY, PASSPHRASE, TRADING_CONFIG, TIME_CONFIG +from config import API_KEY, SECRET_KEY, PASSPHRASE, SANDBOX, TRADING_CONFIG, TIME_CONFIG logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s') @@ -12,7 +12,8 @@ class BizMain: api_key = API_KEY secret_key = SECRET_KEY passphrase = PASSPHRASE - sandbox = TRADING_CONFIG.get("sandbox", True) + # sandbox = TRADING_CONFIG.get("sandbox", True) + sandbox = SANDBOX symbol = TRADING_CONFIG.get("symbol", "BTC-USDT") position_size = TRADING_CONFIG.get("position_size", 0.001) self.trader = QuantTrader(api_key, secret_key, passphrase, sandbox, symbol, position_size) diff --git a/wechat.py b/wechat.py new file mode 100644 index 0000000..5d68af7 --- /dev/null +++ b/wechat.py @@ -0,0 +1,6 @@ +""" +微信群发功能 +建议使用企业微信 +但需要管理员提供企业id以及secret信息 +通过wechatpy库实现 +""" \ No newline at end of file