From dd4bd64a09d32977ba47921d55dd226a0a6cbbc3 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 26 Mar 2026 23:21:32 +0800 Subject: [PATCH] ++ --- fetch-alphas/my_alpha_list.csv | 3 - fetch-alphas/wqb-get-alphas-one-week.py | 333 ++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 3 deletions(-) delete mode 100644 fetch-alphas/my_alpha_list.csv create mode 100644 fetch-alphas/wqb-get-alphas-one-week.py diff --git a/fetch-alphas/my_alpha_list.csv b/fetch-alphas/my_alpha_list.csv deleted file mode 100644 index 2091439..0000000 --- a/fetch-alphas/my_alpha_list.csv +++ /dev/null @@ -1,3 +0,0 @@ -alpha_id,alpha_type,author,settings,regular,dateCreated,dateSubmitted,dateModified,name,favorite,hidden,color,category,tags,classifications,grade,stage,status,is,os,train,test,prod,competitions,themes,pyramids,pyramidThemes,team,osmosisPoints -npkZlgwd,REGULAR,YC93384,"{'instrumentType': 'EQUITY', 'region': 'USA', 'universe': 'TOP3000', 'delay': 1, 'decay': 0, 'neutralization': 'INDUSTRY', 'truncation': 0.01, 'pasteurization': 'ON', 'unitHandling': 'VERIFY', 'nanHandling': 'OFF', 'maxTrade': 'OFF', 'maxPosition': 'OFF', 'language': 'FASTEXPR', 'visualization': False, 'startDate': '2014-01-01', 'endDate': '2023-12-31'}","{'code': 'multiply(last_diff_value(ts_sum(subtract(implied_volatility_call_120, implied_volatility_put_90), 20), 5),ts_mean(volume,20))', 'description': 'Idea: Long volatility skew momentum in high-option-liquidity stocks weighted by trading volume.\nRationale for data used: implied_volatility_call_120, implied_volatility_put_90\nRationale for operators used: multiply, last_diff_value, ts_sum, subtract, ts_mean', 'operatorCount': 5}",2026-02-07T04:08:56-05:00,,2026-02-07T04:45:53-05:00,,False,False,,,[],[],,IS,UNSUBMITTED,"{'pnl': 25004583, 'bookSize': 20000000, 'longCount': 2140, 'shortCount': 787, 'turnover': 0.0744, 'returns': 0.2505, 'drawdown': 0.4119, 'margin': 0.006739, 'sharpe': 1.14, 'fitness': 1.61, 'startDate': '2014-01-01', 'investabilityConstrained': {'pnl': 13948144, 'bookSize': 20000000, 'longCount': 2166, 'shortCount': 763, 'turnover': 0.0627, 'returns': 0.1398, 'drawdown': 0.4277, 'margin': 0.004458, 'fitness': 1.03, 'sharpe': 0.97}, 'riskNeutralized': {'pnl': 17145827, 'bookSize': 20000000, 'longCount': 2140, 'shortCount': 787, 'turnover': 0.0744, 'returns': 0.1718, 'drawdown': 0.3665, 'margin': 0.004621, 'fitness': 1.15, 'sharpe': 0.98}, 'checks': [{'name': 'LOW_SHARPE', 'result': 'WARNING', 'limit': 1.58, 'value': 1.14}, {'name': 'LOW_FITNESS', 'result': 'PASS', 'limit': 1.0, 'value': 1.61}, {'name': 'LOW_TURNOVER', 'result': 'PASS', 'limit': 0.01, 'value': 0.0744}, {'name': 'HIGH_TURNOVER', 'result': 'PASS', 'limit': 0.7, 'value': 0.0744}, {'name': 'CONCENTRATED_WEIGHT', 'result': 'WARNING', 'date': '2017-09-21', 'limit': 0.1, 'value': 0.479658}, {'name': 'LOW_SUB_UNIVERSE_SHARPE', 'result': 'PASS', 'limit': 0.49, 'value': 0.52}, {'name': 'SELF_CORRELATION', 'result': 'PENDING'}, {'name': 'DATA_DIVERSITY', 'result': 'PENDING'}, {'name': 'PROD_CORRELATION', 'result': 'PENDING'}, {'name': 'REGULAR_SUBMISSION', 'result': 'PENDING'}, {'name': 'IS_LADDER_SHARPE', 'result': 'PASS', 'year': 2, 'startDate': '2024-01-02', 'endDate': '2022-01-03', 'limit': 2.02, 'value': 2.06}, {'name': 'POWER_POOL_CORRELATION', 'result': 'PENDING'}, {'name': 'MATCHES_COMPETITION', 'result': 'WARNING', 'competitions': [{'id': 'DCC2026', 'name': 'Data Creation Challenge 2026'}]}, {'result': 'PASS', 'name': 'MATCHES_PYRAMID', 'effective': 2, 'multiplier': 1.1, 'pyramids': [{'name': 'USA/D1/PV', 'multiplier': 1.1}, {'name': 'USA/D1/OPTION', 'multiplier': 1.3}]}, {'result': 'WARNING', 'name': 'MATCHES_THEMES', 'themes': [{'id': 'M4ZYl3D', 'multiplier': 2.0, 'name': 'USA HighTurnover Datasets Theme'}]}, {'result': 'PENDING', 'name': 'MATCHES_THEMES', 'themes': [{'id': 'EDrKz34', 'multiplier': 1.0, 'name': 'All regions/D1 Power Pool Mar`26'}]}, {'name': 'OSMOSIS_ALLOCATION', 'result': 'WARNING'}]}",,,,,,,,,, -LLOgaLvL,REGULAR,YC93384,"{'instrumentType': 'EQUITY', 'region': 'USA', 'universe': 'TOP3000', 'delay': 1, 'decay': 0, 'neutralization': 'INDUSTRY', 'truncation': 0.08, 'pasteurization': 'ON', 'unitHandling': 'VERIFY', 'nanHandling': 'OFF', 'maxTrade': 'OFF', 'maxPosition': 'OFF', 'language': 'FASTEXPR', 'visualization': False, 'startDate': '2014-01-01', 'endDate': '2023-12-31'}","{'code': 'ts_rank(add(ts_count_nans(pv87_2_nav_qf_matrix_all_chngratio_median, 45), ts_count_nans(mdl264_call_put_erlanger_ratio_l3, 45)), 90)', 'description': None, 'operatorCount': 4}",2026-01-19T04:36:02-05:00,,2026-01-19T04:36:02-05:00,,False,False,,,[],[],,IS,UNSUBMITTED,"{'pnl': 10827219, 'bookSize': 20000000, 'longCount': 2486, 'shortCount': 389, 'turnover': 0.0484, 'returns': 0.1085, 'drawdown': 0.183, 'margin': 0.004481, 'sharpe': 1.27, 'fitness': 1.18, 'startDate': '2014-01-01', 'investabilityConstrained': {'pnl': 10545973, 'bookSize': 20000000, 'longCount': 2333, 'shortCount': 797, 'turnover': 0.038, 'returns': 0.1057, 'drawdown': 0.1061, 'margin': 0.005561, 'fitness': 1.27, 'sharpe': 1.38}, 'riskNeutralized': {'pnl': 3060381, 'bookSize': 20000000, 'longCount': 2486, 'shortCount': 389, 'turnover': 0.0484, 'returns': 0.0307, 'drawdown': 0.1434, 'margin': 0.001267, 'fitness': 0.3, 'sharpe': 0.6}, 'checks': [{'name': 'LOW_SHARPE', 'result': 'WARNING', 'limit': 1.58, 'value': 1.27}, {'name': 'LOW_FITNESS', 'result': 'PASS', 'limit': 1.0, 'value': 1.18}, {'name': 'LOW_TURNOVER', 'result': 'PASS', 'limit': 0.01, 'value': 0.0484}, {'name': 'HIGH_TURNOVER', 'result': 'PASS', 'limit': 0.7, 'value': 0.0484}, {'name': 'CONCENTRATED_WEIGHT', 'result': 'PASS'}, {'name': 'LOW_SUB_UNIVERSE_SHARPE', 'result': 'PASS', 'limit': 0.55, 'value': 0.95}, {'name': 'SELF_CORRELATION', 'result': 'PENDING'}, {'name': 'DATA_DIVERSITY', 'result': 'PENDING'}, {'name': 'PROD_CORRELATION', 'result': 'PENDING'}, {'name': 'REGULAR_SUBMISSION', 'result': 'PENDING'}, {'name': 'IS_LADDER_SHARPE', 'result': 'WARNING', 'year': 2, 'startDate': '2024-01-02', 'endDate': '2022-01-03', 'limit': 1.58, 'value': 1.26}, {'name': 'POWER_POOL_CORRELATION', 'result': 'PENDING'}, {'name': 'MATCHES_COMPETITION', 'result': 'WARNING', 'competitions': [{'id': 'DCC2026', 'name': 'Data Creation Challenge 2026'}]}, {'result': 'PASS', 'name': 'MATCHES_PYRAMID', 'effective': 2, 'multiplier': 1.1, 'pyramids': [{'name': 'USA/D1/PV', 'multiplier': 1.1}, {'name': 'USA/D1/MODEL', 'multiplier': 1.4}]}, {'result': 'WARNING', 'name': 'MATCHES_THEMES', 'themes': [{'id': 'M4ZYl3D', 'multiplier': 2.0, 'name': 'USA HighTurnover Datasets Theme'}]}, {'result': 'PENDING', 'name': 'MATCHES_THEMES', 'themes': [{'id': 'EDrKz34', 'multiplier': 1.0, 'name': 'All regions/D1 Power Pool Mar`26'}]}, {'name': 'OSMOSIS_ALLOCATION', 'result': 'WARNING'}]}",,,,,,,,,, diff --git a/fetch-alphas/wqb-get-alphas-one-week.py b/fetch-alphas/wqb-get-alphas-one-week.py new file mode 100644 index 0000000..ccebb58 --- /dev/null +++ b/fetch-alphas/wqb-get-alphas-one-week.py @@ -0,0 +1,333 @@ +import httpx +from httpx import BasicAuth, Timeout +import pandas as pd +from tqdm import tqdm +from pathlib import Path +import logging +from typing import List, Optional +import time +from datetime import datetime, timedelta + +class AlphaManager: + def __init__(self, credentials_file='account.txt', base_path=None): + """ + 初始化Alpha管理器 + + Args: + credentials_file: 账号文件路径(备用) + base_path: 输出文件保存路径 + """ + self.client = None + self.base_path = Path(base_path) if base_path else Path.cwd() + self.logger = self._setup_logger() + + # 登录 + self.login(credentials_file) + + def _setup_logger(self): + """设置日志记录器""" + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + if not logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + def login(self, credentials_file='account.txt'): + """登录WorldQuant Brain API""" + try: + # 从nacos获取账号密码 + with httpx.Client(timeout=10.0) as temp_client: + nacos_resp = temp_client.get( + 'http://192.168.31.41:30848/nacos/v1/cs/configs?dataId=wq_account&group=quantify' + ) + + if nacos_resp.status_code != 200: + self.logger.error('获取账号密码失败') + return False + + config = nacos_resp.json() + username = config.get('user_name') + password = config.get('password') + + if not username or not password: + self.logger.error('账号密码不完整') + return False + + self.logger.info(f"正在登录账户: {username}") + + # 创建客户端并设置超时(关键修复) + # 设置更长的超时时间:连接30秒,读取60秒 + timeout = Timeout(connect=30.0, read=60.0, write=30.0, pool=30.0) + self.client = httpx.Client( + auth=BasicAuth(username, password), + timeout=timeout + ) + + # 发送登录请求 + response = self.client.post('https://api.worldquantbrain.com/authentication') + + if response.status_code == 201: + self.logger.info("登录成功!") + return True + else: + self.logger.error(f"登录失败: {response.status_code} - {response.text}") + self.client.close() + self.client = None + return False + + except Exception as e: + self.logger.error(f"登录异常: {e}") + return False + + def update_alpha_color(self, alpha_id: str, color: str) -> bool: + """标记Alpha颜色""" + if not self.client: + self.logger.error("客户端未登录") + return False + + try: + update_data = {"color": color} + response = self.client.patch( + f"https://api.worldquantbrain.com/alphas/{alpha_id}", + json=update_data + ) + return response.status_code == 200 + except Exception as e: + self.logger.error(f"标记颜色失败: {e}") + return False + + def wechat_check_corr_message(self, message: str): + """微信通知(可选功能)""" + self.logger.info(f"通知消息: {message}") + + def _make_request_with_retry(self, url: str, max_retries: int = 3, retry_delay: float = 2.0): + """ + 带重试机制的请求 + + Args: + url: 请求URL + max_retries: 最大重试次数 + retry_delay: 重试延迟(秒) + + Returns: + httpx.Response 或 None + """ + for attempt in range(max_retries): + try: + response = self.client.get(url) + return response + except Exception as e: + self.logger.warning(f"请求失败 (尝试 {attempt+1}/{max_retries}): {e}") + if attempt < max_retries - 1: + time.sleep(retry_delay) + else: + self.logger.error(f"请求最终失败: {url}") + raise + return None + + def get_alphas( + self, + total_alphas: int = 5, + limit: int = 100, + delay: int = 1, + days_back: int = 7, + required_fields: Optional[List[str]] = None, + match_mode: str = "all", + min_sharpe: Optional[float] = None, + min_fitness: Optional[float] = None, + hidden: str = "false", + submittable: bool = False, + auto_color: bool = False, + color: str = "GREEN", + output_file_name: str = "alpha_search_list.csv", + mode: str = "w", + max_retries: int = 3, + ): + """ + 搜索Alpha并筛选(按时间范围,默认最近一周) + + Args: + total_alphas: 最多获取多少个Alpha + limit: 每次API请求获取的数量 + delay: API请求的延迟设置 + days_back: 查询多少天前的数据(默认7天,即一周) + required_fields: 关键词列表,如 ['put', 'call'] + match_mode: 关键词匹配模式:"all"(全匹配) 或 "any"(任一匹配) + min_sharpe: 最小夏普比率阈值 + min_fitness: 最小适应度阈值 + hidden: 是否搜索已隐藏的Alpha ("true"/"false") + submittable: 是否只筛选可提交(无FAIL检查项)的Alpha + auto_color: 是否自动给符合条件的Alpha标记颜色 + color: 标记的颜色 + output_file_name: 输出的CSV文件名 + mode: 写入模式:"w"(覆盖写入) 或 "a"(追加写入) + max_retries: 请求失败时的最大重试次数 + + Returns: + list: 符合条件的Alpha列表 + """ + if not self.client: + self.logger.error("客户端未登录,无法执行搜索") + return [] + + # 验证颜色参数 + valid_colors = [None, "GREEN", "YELLOW", "RED", "BLUE", "PURPLE", "ORANGE"] + if color not in valid_colors: + raise ValueError(f"颜色必须是以下之一: {valid_colors}") + + # 计算时间范围(美国东部时间 UTC-5,夏令时 UTC-4) + now = datetime.now() + end_date = now + start_date = now - timedelta(days=days_back) + + # 格式化为 ISO 8601 格式(带时区偏移) + # 使用 UTC-4(夏令时),如果是标准时间则改为 -05:00 + start_date_str = start_date.strftime("%Y-%m-%dT%H:%M:%S-04:00") + end_date_str = end_date.strftime("%Y-%m-%dT%H:%M:%S-04:00") + + self.logger.info(f"查询时间范围: {start_date_str} 到 {end_date_str}") + + fetched_alphas = [] + offset = 0 + total_accessed = 0 + colored_count = 0 + + # 先获取总数(带重试) + count_url = ( + f"https://api.worldquantbrain.com/users/self/alphas?stage=IS&hidden={hidden}" + f"&limit=1&status=UNSUBMITTED%1FIS_FAIL" + f"&dateCreated%3E={start_date_str}&dateCreated%3C={end_date_str}" + ) + + total_available = 0 + for attempt in range(max_retries): + try: + count_response = self.client.get(count_url) + total_available = count_response.json()["count"] + break + except Exception as e: + self.logger.warning(f"获取Alpha总数失败 (尝试 {attempt+1}/{max_retries}): {e}") + if attempt < max_retries - 1: + time.sleep(2.0) + else: + self.logger.error("获取Alpha总数最终失败") + return [] + + if total_available == 0: + self.logger.warning("未找到任何Alpha") + return [] + + self.logger.info(f"共找到 {total_available} 个Alpha,开始筛选...") + + pbar = tqdm(total=min(total_available, 10000), desc="扫描Alpha", unit="条") + + while len(fetched_alphas) < total_alphas and offset < total_available: + # 构建请求URL(使用时间范围,移除 region 和 universe) + url = ( + f"https://api.worldquantbrain.com/users/self/alphas?stage=IS&limit={limit}" + f"&offset={offset}&hidden={hidden}&status=UNSUBMITTED%1FIS_FAIL" + f"&dateCreated%3E={start_date_str}&dateCreated%3C={end_date_str}&order=dateCreated" + ) + + try: + # 使用重试机制 + response = None + for attempt in range(max_retries): + try: + response = self.client.get(url) + break + except Exception as e: + self.logger.warning(f"请求失败 (尝试 {attempt+1}/{max_retries}): {e}") + if attempt < max_retries - 1: + time.sleep(2.0) + else: + raise + + if response.status_code == 400: + self.logger.warning(f"遇到API限制 (offset={offset}),停止获取更多数据") + break + + response_data = response.json() + + if not isinstance(response_data, dict) or "results" not in response_data: + self.logger.error(f"API返回了意外的数据: {response_data}") + break + + alphas = response_data["results"] + + if not alphas: + break + + total_accessed += len(alphas) + + # 直接保存所有获取到的Alpha,不做任何筛选 + fetched_alphas.extend(alphas) + + # 更新进度条 + pbar.update(len(alphas)) + pbar.set_postfix({ + "时间范围": f"{days_back}天", + "已扫描": total_accessed, + "已获取": len(fetched_alphas), + }) + + if len(alphas) < limit: + break + + offset += limit + + # 添加延迟避免请求过快 + time.sleep(0.5) + + except Exception as e: + self.logger.error(f"请求失败: {e}") + break + + pbar.close() + + # 获取所有数据,不限制数量 + alpha_list = fetched_alphas + + if not alpha_list: + self.logger.warning("未获取到任何Alpha数据!") + else: + df = pd.DataFrame(alpha_list) + + output_path = self.base_path / output_file_name + + if mode == "w": + df.to_csv(output_path, index=False) + elif mode == "a": + df.to_csv(output_path, mode="a", index=False, header=False) + + self.logger.info(f"一周内Alpha数据下载完成!共{len(alpha_list)}条记录!\n保存至: {output_path}") + + return alpha_list + + def close(self): + """关闭客户端""" + if self.client: + self.client.close() + + +# 使用示例 +if __name__ == "__main__": + # 创建管理器实例(会自动登录) + manager = AlphaManager() + + if manager.client: + # 获取最近一周内的所有Alpha数据(不做任何筛选) + results = manager.get_alphas( + days_back=7, # 查询最近7天(一周)的数据 + output_file_name="alpha_one_week_all.csv" + ) + + print(f"共获取 {len(results) if results else 0} 条Alpha数据") + + # 关闭客户端 + manager.close() \ No newline at end of file