自用策略初始提交
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
# 导入pandas库,用于数据处理和分析,简称pd
|
||||
import pandas as pd
|
||||
# 导入requests库,用于发送HTTP网络请求
|
||||
import requests
|
||||
# 导入json库,用于处理JSON格式的数据
|
||||
import json
|
||||
# 导入warnings库,用于处理警告信息
|
||||
import warnings
|
||||
# 导入yaml库,用于读取配置文件
|
||||
import yaml
|
||||
# 导入os库,用于文件路径操作
|
||||
import os
|
||||
# 忽略所有警告信息,让程序运行更清爽
|
||||
warnings.filterwarnings(action='ignore')
|
||||
# 定义雪球数据获取类
|
||||
class xueqie_data:
|
||||
# 类的初始化方法,创建对象时自动调用
|
||||
def __init__(self, cookie_list=None, config=None):
|
||||
'''
|
||||
雪球数据获取类的初始化方法
|
||||
参数说明:
|
||||
cookie_list: 雪球网站的登录凭证列表,用于模拟用户登录
|
||||
config: 配置信息字典
|
||||
'''
|
||||
print('[初始化] 正在初始化雪球数据获取工具...')
|
||||
|
||||
# 保存配置信息
|
||||
self.config = config if config else load_config()
|
||||
|
||||
# 从配置文件或参数获取cookie列表
|
||||
if cookie_list is not None:
|
||||
self.cookie_list = cookie_list
|
||||
print('[参数] 使用传入的cookie列表')
|
||||
else:
|
||||
# 从配置文件读取cookie列表
|
||||
self.cookie_list = self.config.get('cookie列表', [])
|
||||
print('[配置] 从配置文件读取cookie列表')
|
||||
|
||||
print('[加载] 已加载{}个身份验证信息'.format(len(self.cookie_list)))
|
||||
|
||||
# 创建一个空的DataFrame用于存储获取到的数据
|
||||
self.df=pd.DataFrame()
|
||||
print('[完成] 雪球数据获取工具初始化完成')
|
||||
# 定义获取HTTP请求头的方法
|
||||
def get_headers(self,cookie=''):
|
||||
'''
|
||||
构造访问雪球网站时需要的HTTP请求头信息
|
||||
参数说明:
|
||||
cookie: 用户的登录凭证字符串
|
||||
返回值: 包含所有必要头信息的字典
|
||||
'''
|
||||
# 构造HTTP请求头字典,模拟真实浏览器访问
|
||||
headers={
|
||||
# 告诉服务器可以接受任何类型的响应内容
|
||||
'Accept':'*/*',
|
||||
# 告诉服务器支持的压缩格式,用于减少传输数据量
|
||||
'Accept-Encoding':'gzip, deflate, br, zstd',
|
||||
# 设置语言偏好:首选中文,其次英文
|
||||
'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
# 保持HTTP连接,提高效率
|
||||
'Connection':'keep-alive',
|
||||
# 用户的登录凭证,最重要的认证信息
|
||||
'Cookie':cookie,
|
||||
# 目标网站的域名
|
||||
'Host':'xueqiu.com',
|
||||
# 告诉服务器请求来源页面,防止被识别为爬虫
|
||||
'Referer':'https://xueqiu.com/P/ZH3223683',
|
||||
# 浏览器安全相关的头信息,模拟Chrome/Edge浏览器
|
||||
'Sec-Ch-Ua':'"Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"',
|
||||
# 表示不是移动设备
|
||||
'Sec-Ch-Ua-Mobile':'?0',
|
||||
# 操作系统平台信息
|
||||
'Sec-Ch-Ua-Platform':"Windows",
|
||||
# 请求目标类型
|
||||
'Sec-Fetch-Dest':'empty',
|
||||
# 请求模式,CORS表示跨域请求
|
||||
'Sec-Fetch-Mode':'cors',
|
||||
# 请求来源,same-origin表示同源请求
|
||||
'Sec-Fetch-Site':'same-origin',
|
||||
# 用户代理字符串,模拟Edge浏览器,让服务器认为是真实用户
|
||||
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
||||
# 表示这是一个AJAX请求
|
||||
'X-Requested-With':'XMLHttpRequest'
|
||||
}
|
||||
# 返回构造好的请求头字典
|
||||
return headers
|
||||
# 定义获取最近调仓记录的方法
|
||||
def get_the_latest_move(self,rb_id=''):
|
||||
'''
|
||||
获取投资组合的最近一次调仓记录
|
||||
参数说明:
|
||||
rb_id: 调仓记录的ID,用于获取特定的调仓信息
|
||||
返回值: 包含调仓记录的DataFrame,如果失败则返回空DataFrame
|
||||
API地址示例: https://xueqiu.com/cubes/rebalancing/show_origin.json?rb_id=164306198&cube_symbol=ZH3223683
|
||||
'''
|
||||
# 雪球API的URL地址,用于获取调仓详情
|
||||
url='https://xueqiu.com/cubes/rebalancing/show_origin.json?'
|
||||
# 获取HTTP请求头,但这里没有传入cookie(可能是个bug)
|
||||
headers=self.get_headers()
|
||||
# 构造请求参数
|
||||
params={
|
||||
'rb_id':rb_id, # 调仓记录ID
|
||||
'cube_symbol':self.assembly_id # 投资组合ID(注意:这里使用了未定义的属性)
|
||||
}
|
||||
# 使用try-except处理可能的网络错误
|
||||
try:
|
||||
# 发送GET请求获取数据
|
||||
print('[获取] 正在获取最新调仓信息...')
|
||||
res=requests.get(url=url,headers=headers,params=params)
|
||||
# 将响应转换为JSON格式
|
||||
text=res.json()
|
||||
|
||||
# 检查是否有错误码
|
||||
if 'error_code' in text:
|
||||
print('[错误] 获取最新调仓失败: {}'.format(text.get('error_description', '未知错误')))
|
||||
return pd.DataFrame()
|
||||
|
||||
# 检查API返回的状态
|
||||
stats=text['rebalancing']['status']
|
||||
# 如果请求成功
|
||||
if stats=='success':
|
||||
print('[成功] 最新调仓数据获取成功')
|
||||
# 提取调仓历史记录
|
||||
result=text['rebalancing']['rebalancing_histories']
|
||||
# 转换为DataFrame格式
|
||||
df=pd.DataFrame(result)
|
||||
# <<< 关键:过滤未成交/已取消的单子 >>>
|
||||
print(f'[过滤] 已过滤掉 {len(result) - len(df)} 条未成交/已取消的调仓记录')
|
||||
df = df[df['price'].notna()]
|
||||
# 将时间戳转换为可读的日期时间格式(毫秒级时间戳)
|
||||
df['updated_at']=pd.to_datetime(df['updated_at'],unit='ms')
|
||||
print('[数据] 成功获取{}条最新调仓记录'.format(len(df)))
|
||||
return df
|
||||
else:
|
||||
# 如果请求失败,打印错误信息
|
||||
print('[失败] 最新调仓数据获取失败')
|
||||
print('[响应] 服务器响应: {}'.format(text))
|
||||
print('[建议] 建议检查身份验证信息或稍后重试')
|
||||
# 返回空的DataFrame
|
||||
df=pd.DataFrame()
|
||||
return df
|
||||
# 捕获所有异常
|
||||
except Exception as e:
|
||||
# 打印异常信息
|
||||
print('[异常] 获取最新调仓数据时发生错误: {}'.format(str(e)))
|
||||
print('[建议] 建议检查网络连接或身份验证信息')
|
||||
# 返回空的DataFrame
|
||||
df=pd.DataFrame()
|
||||
return df
|
||||
# 定义获取历史调仓记录的方法
|
||||
def get_hist_move(self,assembly_id=None):
|
||||
'''
|
||||
获取投资组合的历史调仓记录
|
||||
参数说明:
|
||||
assembly_id: 投资组合ID,如果为None则从配置文件读取
|
||||
返回值: 包含所有历史调仓记录的DataFrame
|
||||
API地址示例: https://xueqiu.com/cubes/rebalancing/history.json?cube_symbol=ZH3223683&count=20&page=1
|
||||
'''
|
||||
# 如果没有传入组合ID,从组合配置中读取
|
||||
if assembly_id is None:
|
||||
portfolio_configs = self.config.get('组合配置', [])
|
||||
if portfolio_configs:
|
||||
assembly_id = portfolio_configs[0].get('组合ID', 'ZH3361149')
|
||||
else:
|
||||
assembly_id = 'ZH3361149' # 修正为实际数据中的组合ID
|
||||
print(f'[配置] 从配置文件读取组合ID: {assembly_id}')
|
||||
# 初始化cookie计数器,用于跟踪尝试了第几个cookie
|
||||
j=1
|
||||
# 状态标志,True表示还需要继续尝试,False表示已经成功获取数据
|
||||
stats=True
|
||||
# 遍历所有可用的cookie,尝试获取数据
|
||||
for cookie in self.cookie_list:
|
||||
# 如果还没有成功获取数据,继续尝试
|
||||
if stats:
|
||||
# 雪球历史调仓API的URL地址
|
||||
url='https://xueqiu.com/cubes/rebalancing/history.json?'
|
||||
# 使用当前cookie构造请求头
|
||||
headers=self.get_headers(cookie=cookie)
|
||||
# 构造请求参数
|
||||
params={
|
||||
'cube_symbol':assembly_id, # 投资组合ID
|
||||
'count': '50', # 每页返回50条记录
|
||||
'page': '1', # 获取第1页数据
|
||||
}
|
||||
# 发送GET请求
|
||||
res=requests.get(url=url,headers=headers,params=params)
|
||||
# 将响应转换为JSON格式
|
||||
text=res.json()
|
||||
|
||||
# 获取响应中的所有键名,用于检查是否有错误
|
||||
keys_list=list(set(text.keys()))
|
||||
# 如果响应中没有'error_code'键,说明请求成功
|
||||
if 'error_code' not in keys_list:
|
||||
# 保存原始数据到JSON文件,便于分析数据结构
|
||||
import json
|
||||
with open('原始数据.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(text, f, indent=2, ensure_ascii=False)
|
||||
print('[保存] 雪球原始数据已保存至: 原始数据.json')
|
||||
|
||||
# 将调仓列表转换为DataFrame
|
||||
df=pd.DataFrame(text['list'])
|
||||
# 创建空的DataFrame用于存储所有调仓记录
|
||||
data=pd.DataFrame()
|
||||
# 遍历每个调仓操作及其历史记录
|
||||
for idx, rebalancing in df.iterrows():
|
||||
histories = rebalancing['rebalancing_histories']
|
||||
|
||||
# 将每个调仓历史转换为DataFrame
|
||||
df1 = pd.DataFrame(histories)
|
||||
print(f'[过滤] 已过滤掉 {len(histories) - len(df1)} 条未成交/已取消的调仓记录')
|
||||
# <<< 关键修改:过滤已取消或未成交的单子 >>>
|
||||
df1 = df1[df1['price'].notna()] # 过滤掉 price 为 None 的已取消/未成交单
|
||||
|
||||
# 添加组合ID和组合名字信息
|
||||
if len(df1) > 0:
|
||||
df1['组合ID'] = assembly_id
|
||||
# 获取组合名字
|
||||
portfolio_name = self.get_portfolio_name(assembly_id)
|
||||
df1['组合名字'] = portfolio_name
|
||||
|
||||
# 将所有调仓记录合并到一个DataFrame中
|
||||
data=pd.concat([data,df1],ignore_index=True)
|
||||
print('[成功] 第{}个身份验证成功,开始获取数据...'.format(j))
|
||||
# 设置状态为False,表示已经成功获取数据,不需要再尝试其他cookie
|
||||
stats=False
|
||||
else:
|
||||
# 如果有错误,打印错误信息
|
||||
print('[失败] 第{}个身份验证失败: {}'.format(j, text.get('error_description', '未知错误')))
|
||||
print('[重试] 正在尝试下一个身份验证...')
|
||||
else:
|
||||
# 如果前面已经成功获取数据,跳过后续cookie
|
||||
print('[跳过] 第{}个身份验证跳过(已获取到数据)'.format(j))
|
||||
stats=False
|
||||
# cookie计数器加1
|
||||
j+=1
|
||||
# 检查最终状态
|
||||
if stats:
|
||||
# 如果所有cookie都失败了,这里什么都不做
|
||||
pass
|
||||
else:
|
||||
# 这个打印信息有误,应该是"cookie 有可用的"
|
||||
print('[完成] 数据获取成功,已找到可用的身份验证')
|
||||
# 使用try-except处理可能的变量未定义错误
|
||||
try:
|
||||
# 检查data变量是否存在且有数据
|
||||
if data.shape[0]>=0:
|
||||
data=data
|
||||
else:
|
||||
# 如果没有数据,返回空DataFrame
|
||||
data=pd.DataFrame()
|
||||
except:
|
||||
# 如果data变量未定义,创建空DataFrame
|
||||
data=pd.DataFrame()
|
||||
# 返回获取到的历史调仓数据
|
||||
return data
|
||||
|
||||
def get_portfolio_name(self, assembly_id):
|
||||
"""
|
||||
根据组合ID获取组合名字
|
||||
"""
|
||||
try:
|
||||
if self.config:
|
||||
portfolio_configs = self.config.get('组合配置', [])
|
||||
for config in portfolio_configs:
|
||||
if config.get('组合ID') == assembly_id:
|
||||
return config.get('组合名字', assembly_id)
|
||||
return assembly_id # 如果找不到配置,返回组合ID
|
||||
except Exception as e:
|
||||
print(f'[警告] 获取组合名字失败: {e}')
|
||||
return assembly_id
|
||||
|
||||
# 定义列名翻译映射字典
|
||||
def get_column_translation():
|
||||
'''
|
||||
返回英文列名到中文列名的映射字典
|
||||
'''
|
||||
column_mapping = {
|
||||
'id': '记录ID',
|
||||
'rebalancing_id': '调仓ID',
|
||||
'组合ID': '组合ID',
|
||||
'stock_id': '股票ID',
|
||||
'stock_name': '股票名称',
|
||||
'stock_symbol': '股票代码',
|
||||
'volume': '持仓数量',
|
||||
'price': '价格',
|
||||
'net_value': '净值',
|
||||
'weight': '当前权重',
|
||||
'target_weight': '目标权重',
|
||||
'prev_weight': '前期权重',
|
||||
'prev_target_weight': '前期目标权重',
|
||||
'prev_weight_adjusted': '前期调整权重',
|
||||
'prev_volume': '前期数量',
|
||||
'prev_price': '前期价格',
|
||||
'prev_net_value': '前期净值',
|
||||
'proactive': '主动调仓',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'target_volume': '目标数量',
|
||||
'prev_target_volume': '前期目标数量'
|
||||
}
|
||||
return column_mapping
|
||||
|
||||
# 定义配置文件读取函数
|
||||
def load_config():
|
||||
'''
|
||||
从YAML配置文件读取参数
|
||||
返回配置字典
|
||||
'''
|
||||
config_file = '参数设置.yaml'
|
||||
try:
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f)
|
||||
print(f'[配置] 成功加载配置文件: {config_file}')
|
||||
return config
|
||||
else:
|
||||
print(f'[警告] 配置文件不存在: {config_file}')
|
||||
print('[默认] 使用默认配置')
|
||||
return {'组合ID': 'ZH3361149'}
|
||||
except Exception as e:
|
||||
print(f'[错误] 读取配置文件失败: {str(e)}')
|
||||
print('[默认] 使用默认配置')
|
||||
return {'组合ID': 'ZH3361149'}
|
||||
|
||||
|
||||
|
||||
# 主程序入口,当直接运行此文件时执行
|
||||
if __name__=='__main__':
|
||||
'''
|
||||
雪球投资组合数据获取程序
|
||||
'''
|
||||
print('[启动] 雪球跟单数据获取程序启动...')
|
||||
print('[初始化] 正在初始化数据获取工具...')
|
||||
|
||||
# 从配置文件获取组合配置
|
||||
config = load_config()
|
||||
|
||||
# 获取组合ID列表
|
||||
portfolio_configs = config.get('组合配置', [])
|
||||
if portfolio_configs:
|
||||
assembly_ids = [config.get('组合ID') for config in portfolio_configs if config.get('组合ID')]
|
||||
else:
|
||||
assembly_ids = ['ZH3361149'] # 默认组合ID(修正为实际数据中的组合ID)
|
||||
print(f'[配置] 使用组合ID: {assembly_ids}')
|
||||
|
||||
# 创建雪球数据获取对象
|
||||
trader = xueqie_data(config=config)
|
||||
|
||||
# 合并所有组合的数据
|
||||
all_data = pd.DataFrame()
|
||||
|
||||
for assembly_id in assembly_ids:
|
||||
print(f'[处理] 正在获取组合 {assembly_id} 的数据...')
|
||||
df = trader.get_hist_move(assembly_id=assembly_id)
|
||||
if len(df) > 0:
|
||||
all_data = pd.concat([all_data, df], ignore_index=True)
|
||||
print(f'[成功] 组合 {assembly_id} 获取到 {len(df)} 条记录')
|
||||
else:
|
||||
print(f'[警告] 组合 {assembly_id} 未获取到数据')
|
||||
|
||||
df = all_data
|
||||
# 打印当前进度,让用户了解数据获取状态
|
||||
if len(df) > 0:
|
||||
print('[成功] 数据获取成功!共获取{}条调仓记录'.format(len(df)))
|
||||
print('[保存] 正在保存数据到CSV文件...')
|
||||
|
||||
# 获取列名翻译映射
|
||||
column_mapping = get_column_translation()
|
||||
|
||||
# 翻译列名为中文
|
||||
df_chinese = df.copy()
|
||||
df_chinese.columns = [column_mapping.get(col, col) for col in df.columns]
|
||||
|
||||
# 保存为临时CSV文件,避免覆盖交易系统处理后的数据
|
||||
df_chinese.to_csv('./辅助文件/雪球原始数据.csv', index=False, encoding='utf-8-sig')
|
||||
print('[完成] 雪球原始数据已保存到:雪球原始数据.csv(中文列名)')
|
||||
print('[提示] 交易系统将读取此文件并处理后保存到数据.csv')
|
||||
|
||||
# # 同时保存原始英文列名版本供参考
|
||||
# df.to_csv('数据_英文列名.csv', index=False, encoding='utf-8-sig')
|
||||
# print('[完成] 原始数据已保存到:数据_英文列名.csv(英文列名)')
|
||||
|
||||
else:
|
||||
print('[警告] 本次未获取到数据,可能是网络问题或身份验证失效')
|
||||
|
||||
print('\n[完成] 所有数据获取任务完成!')
|
||||
print('[文件] 请查看工作目录下的 CSV 文件')
|
||||
print(' - 雪球原始数据.csv(雪球原始数据,中文列名版本)')
|
||||
print(' - 数据.csv(交易系统处理后的完整数据)')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user