++
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
id,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,origin
|
||||
rKrVV75J,REGULAR,YC93384,"{'instrumentType': 'EQUITY', 'region': 'USA', 'universe': 'TOP3000', 'delay': 1, 'decay': 0, 'neutralization': 'SLOW_AND_FAST', 'truncation': 0.01, 'pasteurization': 'ON', 'unitHandling': 'VERIFY', 'nanHandling': 'OFF', 'maxTrade': 'ON', 'maxPosition': 'OFF', 'language': 'FASTEXPR', 'visualization': False, 'startDate': '2014-01-01', 'endDate': '2023-12-31'}","{'code': 'subtract(ts_mean(subtract(implied_volatility_call_120, implied_volatility_put_90), 60), reverse(ts_mean(subtract(implied_volatility_call_120, implied_volatility_put_90), 60)))', 'description': None, 'operatorCount': 6}",2026-04-23T11:31:50-04:00,,2026-04-23T11:31:50-04:00,,False,False,,,[],"[{'id': 'DATA_USAGE:SINGLE_DATA_SET', 'name': 'Single Data Set Alpha'}]",,IS,UNSUBMITTED,"{'pnl': 9443011, 'bookSize': 20000000, 'longCount': 1680, 'shortCount': 1449, 'turnover': 0.1125, 'returns': 0.0946, 'drawdown': 0.0431, 'margin': 0.001683, 'sharpe': 1.9, 'fitness': 1.65, 'startDate': '2014-01-01', 'checks': [{'name': 'LOW_SHARPE', 'result': 'PASS', 'limit': 1.58, 'value': 1.9}, {'name': 'LOW_FITNESS', 'result': 'PASS', 'limit': 1.0, 'value': 1.65}, {'name': 'LOW_TURNOVER', 'result': 'PASS', 'limit': 0.01, 'value': 0.1125}, {'name': 'HIGH_TURNOVER', 'result': 'PASS', 'limit': 0.7, 'value': 0.1125}, {'name': 'CONCENTRATED_WEIGHT', 'result': 'PASS'}, {'name': 'LOW_SUB_UNIVERSE_SHARPE', 'result': 'FAIL', 'limit': 0.82, 'value': 0.64}, {'name': 'SELF_CORRELATION', 'result': 'PENDING'}, {'name': 'DATA_DIVERSITY', 'result': 'PENDING'}, {'name': 'PROD_CORRELATION', 'result': 'PENDING'}, {'name': 'REGULAR_SUBMISSION', 'result': 'PENDING'}, {'name': 'HT_TURNOVER', 'result': 'WARNING', 'limit': 0.2, 'value': 0.1125}, {'name': 'HT_HIGH_TURNOVER_RETURNS_RATIO', 'result': 'WARNING', 'limit': 0.75, 'value': 0.3076}, {'name': 'HT_PNL_REALIZATION_HORIZON', 'result': 'WARNING', 'limit': 20, 'value': 76}, {'name': 'LOW_2Y_SHARPE', 'result': 'PASS', 'value': 1.95, 'limit': 1.58}, {'result': 'PASS', 'name': 'MATCHES_PYRAMID', 'effective': 1, 'multiplier': 1.3, 'pyramids': [{'name': 'USA/D1/OPTION', 'multiplier': 1.3}]}, {'result': 'WARNING', 'name': 'MATCHES_THEMES', 'themes': [{'id': 'KymLz14', 'multiplier': 1.0, 'name': 'All regions/D1 Power Pool Apr`26 2'}, {'id': 'lBqMzOB', 'multiplier': 3.0, 'name': 'Investable HTVR Theme'}]}, {'name': 'OSMOSIS_ALLOCATION', 'result': 'WARNING'}]}",,,,,,,,,,,PLATFORM
|
||||
|
@@ -1,264 +0,0 @@
|
||||
import httpx
|
||||
from httpx import BasicAuth, Timeout
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
|
||||
|
||||
class SimpleAlphaFetcher:
|
||||
def __init__(self, base_path=None):
|
||||
"""
|
||||
初始化 Alpha 获取器
|
||||
|
||||
Args:
|
||||
base_path: 输出文件保存路径
|
||||
"""
|
||||
self.client = None
|
||||
self.base_path = Path(base_path) if base_path else Path.cwd()
|
||||
self.logger = self._setup_logger()
|
||||
|
||||
# 登录
|
||||
self.login()
|
||||
|
||||
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):
|
||||
"""登录 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}")
|
||||
|
||||
# 创建客户端并设置超时
|
||||
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 fetch_alphas(
|
||||
self,
|
||||
max_pages: int = 100,
|
||||
limit: int = 100,
|
||||
delay: int = 1,
|
||||
region: str = "USA",
|
||||
universe: str = "TOP3000",
|
||||
hidden: str = "false",
|
||||
output_file_name: str = "alpha_list.csv",
|
||||
mode: str = "w",
|
||||
max_retries: int = 3,
|
||||
):
|
||||
"""
|
||||
获取 Alpha 列表
|
||||
|
||||
Args:
|
||||
max_pages: 最大爬取页数
|
||||
limit: 每页数量
|
||||
delay: API 请求的延迟设置
|
||||
region: 市场区域
|
||||
universe: 股票池
|
||||
hidden: 是否搜索已隐藏的 Alpha
|
||||
output_file_name: 输出的 CSV 文件名
|
||||
mode: 写入模式:"w"(覆盖写入) 或 "a"(追加写入)
|
||||
max_retries: 请求失败时的最大重试次数
|
||||
|
||||
Returns:
|
||||
list: Alpha 列表
|
||||
"""
|
||||
if not self.client:
|
||||
self.logger.error("客户端未登录,无法执行获取")
|
||||
return []
|
||||
|
||||
fetched_alphas = []
|
||||
offset = 0
|
||||
total_accessed = 0
|
||||
pages_fetched = 0
|
||||
|
||||
# 先获取总数
|
||||
count_url = (
|
||||
f"https://api.worldquantbrain.com/users/self/alphas?stage=IS&hidden={hidden}"
|
||||
f"&limit=1&settings.delay={delay}&settings.region={region}&status=UNSUBMITTED%1FIS_FAIL&settings.universe={universe}"
|
||||
)
|
||||
|
||||
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(random.uniform(3, 5))
|
||||
else:
|
||||
self.logger.error("获取 Alpha 总数最终失败")
|
||||
return []
|
||||
|
||||
if total_available == 0:
|
||||
self.logger.warning("未找到任何 Alpha")
|
||||
return []
|
||||
|
||||
# 计算实际最大页数
|
||||
actual_max_pages = min(max_pages, (total_available + limit - 1) // limit)
|
||||
self.logger.info(f"共找到 {total_available} 个 Alpha,计划爬取 {actual_max_pages} 页...")
|
||||
|
||||
pbar = tqdm(total=actual_max_pages, desc="爬取 Alpha 页面", unit="页")
|
||||
|
||||
while pages_fetched < actual_max_pages:
|
||||
# 构建请求 URL
|
||||
url = (
|
||||
f"https://api.worldquantbrain.com/users/self/alphas?stage=IS&limit={limit}"
|
||||
f"&offset={offset}&settings.delay={delay}&settings.region={region}&hidden={hidden}&status=UNSUBMITTED%1FIS_FAIL&settings.universe={universe}"
|
||||
)
|
||||
|
||||
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(random.uniform(3, 5))
|
||||
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
|
||||
|
||||
fetched_alphas.extend(alphas)
|
||||
total_accessed += len(alphas)
|
||||
pages_fetched += 1
|
||||
|
||||
# 更新进度条
|
||||
pbar.update(1)
|
||||
pbar.set_postfix({
|
||||
"Region": region,
|
||||
"已获取": total_accessed,
|
||||
"本页": len(alphas),
|
||||
})
|
||||
|
||||
if len(alphas) < limit:
|
||||
self.logger.info(f"本页数据不足 {limit} 条,已到达末尾")
|
||||
break
|
||||
|
||||
offset += limit
|
||||
|
||||
# 添加延迟避免请求过快
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"请求失败: {e}")
|
||||
break
|
||||
|
||||
pbar.close()
|
||||
|
||||
if not fetched_alphas:
|
||||
self.logger.warning("未获取到任何 Alpha!")
|
||||
return []
|
||||
|
||||
# 按 fitness 排序
|
||||
df = pd.DataFrame(fetched_alphas)
|
||||
df["temp_fitness"] = df.apply(
|
||||
lambda row: row["is"].get("fitness", 0) if isinstance(row.get("is"), dict) else 0,
|
||||
axis=1
|
||||
)
|
||||
df_sorted = df.sort_values(by="temp_fitness", ascending=False)
|
||||
df_sorted = df_sorted.drop("temp_fitness", axis=1)
|
||||
|
||||
output_path = self.base_path / output_file_name
|
||||
|
||||
if mode == "w":
|
||||
df_sorted.to_csv(output_path, index=False)
|
||||
elif mode == "a":
|
||||
df_sorted.to_csv(output_path, mode="a", index=False, header=False)
|
||||
|
||||
self.logger.info(f"Alpha 列表已保存!共 {len(fetched_alphas)} 条记录!\n文件路径: {output_path}")
|
||||
|
||||
return fetched_alphas
|
||||
|
||||
def close(self):
|
||||
"""关闭客户端"""
|
||||
if self.client:
|
||||
self.client.close()
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 创建获取器实例(会自动登录)
|
||||
fetcher = SimpleAlphaFetcher()
|
||||
|
||||
if fetcher.client:
|
||||
# 获取 Alpha 列表
|
||||
results = fetcher.fetch_alphas(
|
||||
max_pages=100, # 最多爬取 100 页
|
||||
limit=100, # 每页 100 条
|
||||
region="USA", # 美股市场
|
||||
universe="TOP3000", # TOP3000 股票池
|
||||
output_file_name="alpha_list.csv" # 输出文件名
|
||||
)
|
||||
|
||||
print(f"成功获取 {len(results) if results else 0} 个 Alpha")
|
||||
|
||||
# 关闭客户端
|
||||
fetcher.close()
|
||||
@@ -0,0 +1,385 @@
|
||||
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
|
||||
|
||||
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=None):
|
||||
"""登录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,
|
||||
region: str = "USA",
|
||||
universe: str = "TOP3000",
|
||||
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请求的延迟设置
|
||||
region: 市场区域,如 "USA", "CHINA"
|
||||
universe: 股票池,如 "TOP3000"
|
||||
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}")
|
||||
|
||||
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&settings.delay={delay}&settings.region={region}&status=UNSUBMITTED%1FIS_FAIL&settings.universe={universe}"
|
||||
)
|
||||
|
||||
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
|
||||
url = (
|
||||
f"https://api.worldquantbrain.com/users/self/alphas?stage=IS&limit={limit}"
|
||||
f"&offset={offset}&settings.delay={delay}&settings.region={region}&hidden={hidden}&status=UNSUBMITTED%1FIS_PASS&settings.universe={universe}"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# 关键词筛选
|
||||
if required_fields:
|
||||
if match_mode == "all":
|
||||
filtered_alphas = [
|
||||
alpha for alpha in alphas
|
||||
if all(field in alpha["regular"]["code"] for field in required_fields)
|
||||
]
|
||||
elif match_mode == "any":
|
||||
filtered_alphas = [
|
||||
alpha for alpha in alphas
|
||||
if any(field in alpha["regular"]["code"] for field in required_fields)
|
||||
]
|
||||
else:
|
||||
raise ValueError("match_mode 必须是 'all' 或 'any'")
|
||||
else:
|
||||
filtered_alphas = alphas
|
||||
|
||||
# 进一步筛选夏普比率和fitness
|
||||
final_filtered = []
|
||||
for alpha in filtered_alphas:
|
||||
sharpe = alpha.get("is", {}).get("sharpe", 0)
|
||||
fitness = alpha.get("is", {}).get("fitness", 0)
|
||||
|
||||
sharpe_ok = (min_sharpe is None) or (sharpe is not None and abs(sharpe) >= min_sharpe)
|
||||
fitness_ok = (min_fitness is None) or (fitness is not None and abs(fitness) >= min_fitness)
|
||||
|
||||
if sharpe_ok and fitness_ok:
|
||||
if submittable:
|
||||
checks = alpha.get("is", {}).get("checks", [])
|
||||
fail_count = sum(1 for check in checks if check.get("result") == "FAIL")
|
||||
|
||||
if fail_count == 0 and auto_color:
|
||||
alpha_id = alpha.get("id")
|
||||
if alpha_id:
|
||||
success = self.update_alpha_color(alpha_id, color)
|
||||
if success:
|
||||
colored_count += 1
|
||||
|
||||
if fail_count <= 1:
|
||||
final_filtered.append(alpha)
|
||||
else:
|
||||
final_filtered.append(alpha)
|
||||
|
||||
fetched_alphas.extend(final_filtered)
|
||||
|
||||
# 更新进度条
|
||||
pbar.update(len(alphas))
|
||||
pbar.set_postfix({
|
||||
"Region": region,
|
||||
"已扫描": total_accessed,
|
||||
"找到": len(fetched_alphas),
|
||||
"本次": len(final_filtered),
|
||||
"标记": colored_count,
|
||||
})
|
||||
|
||||
if len(alphas) < limit:
|
||||
break
|
||||
|
||||
offset += limit
|
||||
|
||||
# 添加延迟避免请求过快
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"请求失败: {e}")
|
||||
break
|
||||
|
||||
pbar.close()
|
||||
|
||||
# 输出标记统计
|
||||
if auto_color and colored_count > 0:
|
||||
self.logger.info(f"共标记了 {colored_count} 个Alpha颜色为{color}")
|
||||
self.wechat_check_corr_message(f"共标记了 {colored_count} 个Alpha颜色为{color}")
|
||||
|
||||
alpha_list = fetched_alphas[:total_alphas]
|
||||
|
||||
if not alpha_list:
|
||||
self.logger.warning("未找到任何符合条件的Alpha!请检查筛选条件是否过于严格。")
|
||||
else:
|
||||
df = pd.DataFrame(alpha_list)
|
||||
df["temp_fitness"] = df.apply(
|
||||
lambda row: row["is"].get("fitness", 0) if isinstance(row.get("is"), dict) else 0,
|
||||
axis=1
|
||||
)
|
||||
df_sorted = df.sort_values(by="temp_fitness", ascending=False)
|
||||
df_sorted = df_sorted.drop("temp_fitness", axis=1)
|
||||
|
||||
output_path = self.base_path / output_file_name
|
||||
|
||||
if mode == "w":
|
||||
df_sorted.to_csv(output_path, index=False)
|
||||
elif mode == "a":
|
||||
df_sorted.to_csv(output_path, mode="a", index=False, header=False)
|
||||
|
||||
self.logger.info(f"批量回测初步检测结果已经下载!共{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:
|
||||
# 搜索包含"put"关键词的可提交Alpha
|
||||
results = manager.get_alphas(
|
||||
total_alphas=20,
|
||||
region="",
|
||||
universe="",
|
||||
required_fields=['put'],
|
||||
match_mode="all",
|
||||
submittable=True,
|
||||
auto_color=True,
|
||||
min_sharpe = 1.0,
|
||||
min_fitness = 0.6,
|
||||
color="GREEN",
|
||||
output_file_name="my_alpha_list.csv"
|
||||
)
|
||||
|
||||
print(f"找到 {len(results) if results else 0} 个符合条件的Alpha")
|
||||
|
||||
# 关闭客户端
|
||||
manager.close()
|
||||
@@ -135,7 +135,7 @@ class AlphaManager:
|
||||
limit: int = 100,
|
||||
delay: int = 1,
|
||||
region: str = "USA",
|
||||
universe: str = "TOP3000",
|
||||
universe: str = "TOP500",
|
||||
required_fields: Optional[List[str]] = None,
|
||||
match_mode: str = "all",
|
||||
min_sharpe: Optional[float] = None,
|
||||
@@ -371,6 +371,8 @@ if __name__ == "__main__":
|
||||
match_mode="all",
|
||||
submittable=True,
|
||||
auto_color=True,
|
||||
min_sharpe = None,
|
||||
min_fitness = None,
|
||||
color="GREEN",
|
||||
output_file_name="my_alpha_list.csv"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user