自用策略初始提交
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
# 导入必要的库
|
||||
import pandas as pd
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
from datetime import datetime
|
||||
import yaml
|
||||
|
||||
def load_config():
|
||||
"""加载配置文件"""
|
||||
config_path = '参数设置.yaml'
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
else:
|
||||
print(f'[警告] 配置文件 {config_path} 不存在,使用默认配置')
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
class XueqiuMonitor:
|
||||
"""
|
||||
雪球调仓监控器 - 基于记录ID去重
|
||||
功能:每3秒执行xueqiu.py,计算调仓幅度,基于记录ID去重
|
||||
"""
|
||||
|
||||
def __init__(self, config=None):
|
||||
"""
|
||||
初始化监控器
|
||||
"""
|
||||
print('[初始化] 雪球调仓监控器初始化中...')
|
||||
|
||||
# 加载配置文件
|
||||
if config is None:
|
||||
self.config = self.load_config()
|
||||
else:
|
||||
self.config = config
|
||||
|
||||
self.monitor_interval = self.config.get('监控间隔', 3)
|
||||
|
||||
# 从组合配置中获取组合ID列表
|
||||
portfolio_configs = self.config.get('组合配置', [])
|
||||
if portfolio_configs:
|
||||
self.assembly_id = [config.get('组合ID') for config in portfolio_configs if config.get('组合ID')]
|
||||
if not self.assembly_id:
|
||||
print('[错误] 配置文件中未找到有效的组合ID')
|
||||
raise ValueError("配置文件中未找到有效的组合ID")
|
||||
else:
|
||||
self.assembly_id = ['ZH3361149'] # 默认组合ID(修正为实际数据中的组合ID)
|
||||
print('[警告] 配置文件中未找到组合配置,使用默认组合ID: ZH3361149')
|
||||
|
||||
# 如果只有一个组合,保持向后兼容
|
||||
if len(self.assembly_id) == 1:
|
||||
self.assembly_id = self.assembly_id[0]
|
||||
|
||||
# 初始化数据存储
|
||||
self.accumulated_data = pd.DataFrame() # 累积数据
|
||||
self.accumulated_data_en = pd.DataFrame() # 累积数据(英文列名)
|
||||
self.processed_record_ids = set() # 已处理的记录ID集合
|
||||
|
||||
# 统计信息
|
||||
self.execution_count = 0 # 执行次数
|
||||
self.total_new_records = 0 # 累积新增记录数
|
||||
|
||||
print('[初始化] 雪球调仓监控器初始化完成')
|
||||
print(f'[设置] 组合ID: {self.assembly_id}')
|
||||
print(f'[设置] 监控间隔: {self.monitor_interval}秒')
|
||||
print('[设置] 去重方式: 基于记录ID')
|
||||
print('[设置] 调仓幅度计算: 目标权重 - 前期调整权重')
|
||||
|
||||
# 加载现有数据
|
||||
self.load_existing_data()
|
||||
|
||||
def load_config(self):
|
||||
"""
|
||||
从YAML配置文件读取参数
|
||||
"""
|
||||
config = load_config()
|
||||
|
||||
|
||||
|
||||
# 设置默认值
|
||||
default_config = {
|
||||
'监控间隔': 3,
|
||||
'累积数据文件': '累积数据_ID去重.csv',
|
||||
'已处理ID文件': '已处理记录ID.txt'
|
||||
}
|
||||
|
||||
# 合并配置
|
||||
for key, value in default_config.items():
|
||||
if key not in config:
|
||||
config[key] = value
|
||||
|
||||
print(f'[配置] 成功加载配置文件')
|
||||
|
||||
return config
|
||||
|
||||
def load_existing_data(self):
|
||||
"""
|
||||
从原始数据.csv文件加载现有数据和已处理的记录ID
|
||||
"""
|
||||
try:
|
||||
# 加载原始数据.csv文件
|
||||
if os.path.exists('./辅助文件/数据.csv'):
|
||||
self.accumulated_data = pd.read_csv('./辅助文件/数据.csv', encoding='utf-8-sig')
|
||||
print(f'[加载] 已加载现有数据: {len(self.accumulated_data)} 条记录')
|
||||
|
||||
# 提取已有的记录ID
|
||||
if '记录ID' in self.accumulated_data.columns:
|
||||
self.processed_record_ids = set(self.accumulated_data['记录ID'].astype(str))
|
||||
print(f'[记录] 已记录 {len(self.processed_record_ids)} 个记录ID')
|
||||
|
||||
# 加载英文列名版本
|
||||
if os.path.exists('./辅助文件/数据_英文列名.csv'):
|
||||
self.accumulated_data_en = pd.read_csv('./辅助文件/数据_英文列名.csv', encoding='utf-8-sig')
|
||||
print(f'[加载] 已加载英文版本数据: {len(self.accumulated_data_en)} 条记录')
|
||||
|
||||
if len(self.processed_record_ids) == 0:
|
||||
print('[状态] 首次运行,将获取所有历史数据')
|
||||
else:
|
||||
print(f'[状态] 继续监控,已处理 {len(self.processed_record_ids)} 条记录')
|
||||
|
||||
except Exception as e:
|
||||
print(f'[错误] 加载现有数据失败: {str(e)}')
|
||||
print('[继续] 将从空数据开始监控')
|
||||
|
||||
def execute_xueqiu_script(self):
|
||||
"""
|
||||
执行xueqiu.py脚本
|
||||
返回: (是否成功, 错误信息)
|
||||
"""
|
||||
try:
|
||||
# 执行xueqiu.py脚本(修正路径为源代码目录)
|
||||
result = subprocess.run(['python', './源代码/xueqiu.py'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='gbk')
|
||||
|
||||
if result.returncode == 0:
|
||||
return True, None
|
||||
else:
|
||||
return False, result.stderr
|
||||
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def calculate_adjustment_amplitude(self, df):
|
||||
"""
|
||||
计算调仓幅度
|
||||
调仓幅度 = 目标权重 - 前期调整权重(为空按0计算)
|
||||
"""
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
df_copy = df.copy()
|
||||
|
||||
# 处理前期调整权重为空的情况
|
||||
if '前期调整权重' in df_copy.columns:
|
||||
df_copy['前期调整权重'] = df_copy['前期调整权重'].fillna(0)
|
||||
else:
|
||||
df_copy['前期调整权重'] = 0
|
||||
|
||||
# 计算调仓幅度
|
||||
if '目标权重' in df_copy.columns:
|
||||
df_copy['调仓幅度'] = df_copy['目标权重'] - df_copy['前期调整权重']
|
||||
else:
|
||||
df_copy['调仓幅度'] = 0
|
||||
|
||||
return df_copy
|
||||
|
||||
def calculate_adjustment_amplitude_en(self, df):
|
||||
"""
|
||||
计算调仓幅度(英文列名版本)
|
||||
"""
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
df_copy = df.copy()
|
||||
|
||||
# 处理前期调整权重为空的情况
|
||||
if 'prev_weight_adjusted' in df_copy.columns:
|
||||
df_copy['prev_weight_adjusted'] = df_copy['prev_weight_adjusted'].fillna(0)
|
||||
else:
|
||||
df_copy['prev_weight_adjusted'] = 0
|
||||
|
||||
# 计算调仓幅度
|
||||
if 'target_weight' in df_copy.columns:
|
||||
df_copy['adjustment_amplitude'] = df_copy['target_weight'] - df_copy['prev_weight_adjusted']
|
||||
else:
|
||||
df_copy['adjustment_amplitude'] = 0
|
||||
|
||||
return df_copy
|
||||
|
||||
def filter_new_data(self, new_data):
|
||||
"""
|
||||
基于记录ID筛选新数据
|
||||
"""
|
||||
if new_data.empty or '记录ID' not in new_data.columns:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 转换记录ID为字符串进行比较
|
||||
new_data['记录ID_str'] = new_data['记录ID'].astype(str)
|
||||
|
||||
# 筛选出未处理过的记录ID
|
||||
filtered_data = new_data[~new_data['记录ID_str'].isin(self.processed_record_ids)]
|
||||
|
||||
# 删除临时列
|
||||
if len(filtered_data) > 0:
|
||||
filtered_data = filtered_data.drop('记录ID_str', axis=1)
|
||||
|
||||
return filtered_data
|
||||
|
||||
def filter_new_data_en(self, new_data_en):
|
||||
"""
|
||||
基于记录ID筛选新数据(英文列名版本)
|
||||
"""
|
||||
if new_data_en.empty or 'id' not in new_data_en.columns:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 转换记录ID为字符串进行比较
|
||||
new_data_en['id_str'] = new_data_en['id'].astype(str)
|
||||
|
||||
# 筛选出未处理过的记录ID
|
||||
filtered_data_en = new_data_en[~new_data_en['id_str'].isin(self.processed_record_ids)]
|
||||
|
||||
# 删除临时列
|
||||
if len(filtered_data_en) > 0:
|
||||
filtered_data_en = filtered_data_en.drop('id_str', axis=1)
|
||||
|
||||
return filtered_data_en
|
||||
|
||||
def save_accumulated_data(self):
|
||||
"""
|
||||
直接更新原始数据.csv文件,不创建新的累积数据文件
|
||||
"""
|
||||
try:
|
||||
# 直接更新原始数据.csv文件
|
||||
if not self.accumulated_data.empty:
|
||||
self.accumulated_data.to_csv('./辅助文件/数据.csv', index=False, encoding='utf-8-sig')
|
||||
print(f'[保存] 数据已更新到原始文件: ./辅助文件/数据.csv ({len(self.accumulated_data)} 条记录)')
|
||||
|
||||
# 保存英文列名版本(如果需要)
|
||||
if not self.accumulated_data_en.empty:
|
||||
self.accumulated_data_en.to_csv('./辅助文件/数据_英文列名.csv', index=False, encoding='utf-8-sig')
|
||||
print(f'[保存] 英文版本数据已保存: ./辅助文件/数据_英文列名.csv ({len(self.accumulated_data_en)} 条记录)')
|
||||
|
||||
except Exception as e:
|
||||
print(f'[错误] 保存数据失败: {str(e)}')
|
||||
|
||||
def process_new_data(self):
|
||||
"""
|
||||
处理新获取的数据
|
||||
"""
|
||||
try:
|
||||
# 读取xueqiu.py生成的数据文件
|
||||
if not os.path.exists('./辅助文件/数据.csv'):
|
||||
print('[警告] 未找到数据.csv文件')
|
||||
return
|
||||
|
||||
# 读取新数据
|
||||
new_data = pd.read_csv('./辅助文件/数据.csv', encoding='utf-8-sig')
|
||||
new_data_en = pd.read_csv('./辅助文件/数据_英文列名.csv', encoding='utf-8-sig') if os.path.exists('./辅助文件/数据_英文列名.csv') else pd.DataFrame()
|
||||
|
||||
print(f'[读取] 本次获取 {len(new_data)} 条记录')
|
||||
|
||||
# 添加组合ID列
|
||||
if '组合ID' not in new_data.columns:
|
||||
new_data['组合ID'] = self.assembly_id
|
||||
if len(new_data_en) > 0 and 'assembly_id' not in new_data_en.columns:
|
||||
new_data_en['assembly_id'] = self.assembly_id
|
||||
|
||||
# 计算调仓幅度
|
||||
new_data = self.calculate_adjustment_amplitude(new_data)
|
||||
new_data_en = self.calculate_adjustment_amplitude_en(new_data_en) if len(new_data_en) > 0 else pd.DataFrame()
|
||||
|
||||
print(f'[计算] 已计算调仓幅度,共 {len(new_data)} 条记录')
|
||||
|
||||
# 筛选新数据(基于记录ID去重)
|
||||
filtered_data = self.filter_new_data(new_data)
|
||||
filtered_data_en = self.filter_new_data_en(new_data_en) if len(new_data_en) > 0 else pd.DataFrame()
|
||||
|
||||
if len(filtered_data) > 0:
|
||||
# 添加新记录到累积数据
|
||||
if self.accumulated_data.empty:
|
||||
self.accumulated_data = filtered_data.copy()
|
||||
else:
|
||||
self.accumulated_data = pd.concat([self.accumulated_data, filtered_data], ignore_index=True)
|
||||
|
||||
# 添加英文版本数据
|
||||
if len(filtered_data_en) > 0:
|
||||
if self.accumulated_data_en.empty:
|
||||
self.accumulated_data_en = filtered_data_en.copy()
|
||||
else:
|
||||
self.accumulated_data_en = pd.concat([self.accumulated_data_en, filtered_data_en], ignore_index=True)
|
||||
|
||||
# 更新已处理的记录ID集合
|
||||
if '记录ID' in filtered_data.columns:
|
||||
new_ids = set(filtered_data['记录ID'].astype(str))
|
||||
self.processed_record_ids.update(new_ids)
|
||||
|
||||
new_records_count = len(filtered_data)
|
||||
self.total_new_records += new_records_count
|
||||
print(f'[新增] 发现 {new_records_count} 条新记录')
|
||||
|
||||
# 显示新增记录的详细信息
|
||||
for _, row in filtered_data.iterrows():
|
||||
adjustment = row.get('调仓幅度', 0)
|
||||
print(f' - {row["股票名称"]} ({row["股票代码"]}): 调仓幅度 {adjustment:.2f}%')
|
||||
|
||||
# 保存累积数据
|
||||
self.save_accumulated_data()
|
||||
|
||||
else:
|
||||
print('[检查] 未发现新记录(所有记录ID已存在)')
|
||||
|
||||
except Exception as e:
|
||||
print(f'[异常] 处理新数据时发生错误: {str(e)}')
|
||||
|
||||
def print_status(self):
|
||||
"""
|
||||
打印监控状态信息
|
||||
"""
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f'\n[状态] 监控状态 [{current_time}]')
|
||||
print(f' [周期] 执行周期: {self.execution_count}')
|
||||
print(f' [新增] 本次新增: {self.total_new_records - (0 if self.execution_count == 1 else getattr(self, "prev_total", 0))} 条')
|
||||
print(f' [累积] 累积记录: {len(self.accumulated_data)} 条')
|
||||
print(f' [ID数] 已处理ID: {len(self.processed_record_ids)} 个')
|
||||
print('-' * 50)
|
||||
|
||||
# 保存当前总数用于下次计算新增
|
||||
self.prev_total = self.total_new_records
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
启动监控循环
|
||||
"""
|
||||
print('[启动] 开始监控雪球调仓数据...')
|
||||
print('[提示] 按 Ctrl+C 停止监控')
|
||||
print('[间隔] 每 3 秒执行一次')
|
||||
print('=' * 50)
|
||||
|
||||
try:
|
||||
while True:
|
||||
self.execution_count += 1
|
||||
current_time = datetime.now().strftime('%H:%M:%S')
|
||||
|
||||
print(f'\n[执行] 第 {self.execution_count} 次执行 [{current_time}]')
|
||||
|
||||
# 执行xueqiu.py脚本
|
||||
success, error = self.execute_xueqiu_script()
|
||||
|
||||
if success:
|
||||
print('[成功] xueqiu.py 执行成功')
|
||||
# 处理新数据
|
||||
self.process_new_data()
|
||||
else:
|
||||
print(f'[失败] xueqiu.py 执行失败: {error}')
|
||||
|
||||
# 打印状态信息
|
||||
self.print_status()
|
||||
|
||||
# 等待指定间隔时间
|
||||
print(f'[等待] 等待 {self.monitor_interval} 秒...')
|
||||
time.sleep(self.monitor_interval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('\n[停止] 监控已停止')
|
||||
print(f'[统计] 总执行次数: {self.execution_count}')
|
||||
print(f'[统计] 累积记录数: {len(self.accumulated_data)}')
|
||||
print(f'[统计] 已处理ID数: {len(self.processed_record_ids)}')
|
||||
|
||||
# 最后保存一次数据
|
||||
if not self.accumulated_data.empty:
|
||||
self.save_accumulated_data()
|
||||
print('[保存] 数据已保存完成')
|
||||
|
||||
print('[完成] 程序已安全退出')
|
||||
|
||||
except Exception as e:
|
||||
print(f'[异常] 程序异常: {str(e)}')
|
||||
# 异常时也要保存数据
|
||||
if not self.accumulated_data.empty:
|
||||
self.save_accumulated_data()
|
||||
print('[保存] 异常退出前已保存数据')
|
||||
|
||||
def main():
|
||||
# 加载配置
|
||||
config = load_config()
|
||||
|
||||
|
||||
|
||||
# 获取配置参数
|
||||
portfolio_configs = config.get('组合配置', [])
|
||||
if not portfolio_configs:
|
||||
print('[错误] 配置文件中未找到组合配置')
|
||||
return
|
||||
|
||||
assembly_id = portfolio_configs[0].get('组合ID')
|
||||
if not assembly_id:
|
||||
print('[错误] 配置文件中未找到有效的组合ID')
|
||||
return
|
||||
|
||||
monitor_interval = config.get('监控间隔', 3)
|
||||
|
||||
print(f'[启动] 雪球组合监控程序')
|
||||
print(f'[配置] 组合ID: {assembly_id}')
|
||||
print(f'[配置] 监控间隔: {monitor_interval}秒')
|
||||
|
||||
# 创建监控对象
|
||||
monitor = XueqiuMonitor(config)
|
||||
|
||||
# 开始监控
|
||||
monitor.run()
|
||||
|
||||
# 主程序入口
|
||||
if __name__ == '__main__':
|
||||
print('[程序] 雪球调仓监控器 - 基于记录ID去重版本')
|
||||
print('[版本] v1.0 - ID去重 + 调仓幅度计算')
|
||||
print('=' * 60)
|
||||
|
||||
# 启动主函数
|
||||
main()
|
||||
Reference in New Issue
Block a user