auto-sync: 2026-04-17 20:23:13
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env python3
|
||||
# 获取A股股票列表 - 循环回流测试任务
|
||||
# 包含:股票代码、股票名称、当前价格
|
||||
# 任务ID: circulation-test-002
|
||||
# 执行人: 赵云(数据护军)
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 添加上级目录到路径,以便导入common_tools
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from common_tools.akshare_vnpy_adapter import AKShareDataAdapter
|
||||
|
||||
class AStockListFetcher:
|
||||
"""A股股票列表获取器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化"""
|
||||
self.adapter = AKShareDataAdapter()
|
||||
self.ak = self.adapter.ak
|
||||
self.akshare_available = self.adapter.akshare_available
|
||||
|
||||
def get_all_a_stocks(self) -> pd.DataFrame:
|
||||
"""获取所有A股股票列表
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 包含代码、名称、当前价格的DataFrame
|
||||
"""
|
||||
logger.info("开始获取A股股票列表...")
|
||||
|
||||
try:
|
||||
if self.akshare_available:
|
||||
# 使用akshare获取A股股票列表
|
||||
stocks_df = self.ak.stock_zh_a_spot()
|
||||
logger.info(f"成功获取A股股票列表,共 {len(stocks_df)} 只股票")
|
||||
|
||||
# 整理列名,选择需要的字段
|
||||
# akshare返回的列名:代码,名称,最新价,涨跌幅,涨跌额,买入,卖出,成交量,成交额,开盘,最高,最低,昨收
|
||||
result_df = pd.DataFrame()
|
||||
result_df['code'] = stocks_df['代码']
|
||||
result_df['name'] = stocks_df['名称']
|
||||
result_df['current_price'] = stocks_df['最新价']
|
||||
result_df['change_percent'] = stocks_df['涨跌幅']
|
||||
result_df['change_amount'] = stocks_df['涨跌额']
|
||||
result_df['volume'] = stocks_df['成交量']
|
||||
result_df['amount'] = stocks_df['成交额']
|
||||
result_df['open'] = stocks_df['开盘']
|
||||
result_df['high'] = stocks_df['最高']
|
||||
result_df['low'] = stocks_df['最低']
|
||||
result_df['pre_close'] = stocks_df['昨收']
|
||||
|
||||
else:
|
||||
# AKShare不可用,生成模拟数据
|
||||
logger.warning("AKShare不可用,生成模拟测试数据")
|
||||
result_df = self._generate_mock_data()
|
||||
|
||||
# 去重处理(防止重复)
|
||||
result_df = result_df.drop_duplicates(subset=['code'], keep='first')
|
||||
# 按代码排序
|
||||
result_df = result_df.sort_values('code').reset_index(drop=True)
|
||||
|
||||
logger.info(f"A股股票列表处理完成,最终 {len(result_df)} 只股票")
|
||||
return result_df
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取A股股票列表失败: {e}")
|
||||
# 返回空DataFrame
|
||||
return pd.DataFrame()
|
||||
|
||||
def _generate_mock_data(self) -> pd.DataFrame:
|
||||
"""生成模拟数据用于测试
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 模拟股票数据
|
||||
"""
|
||||
# 一些代表性的股票作为模拟数据
|
||||
mock_stocks = [
|
||||
{'code': '000001', 'name': '平安银行', 'current_price': 11.25},
|
||||
{'code': '000002', 'name': '万科A', 'current_price': 12.38},
|
||||
{'code': '002594', 'name': '比亚迪', 'current_price': 235.60},
|
||||
{'code': '600000', 'name': '浦发银行', 'current_price': 7.89},
|
||||
{'code': '600519', 'name': '贵州茅台', 'current_price': 1688.00},
|
||||
{'code': '601318', 'name': '中国平安', 'current_price': 42.35},
|
||||
{'code': '601899', 'name': '紫金矿业', 'current_price': 10.26},
|
||||
{'code': '600036', 'name': '招商银行', 'current_price': 31.28},
|
||||
{'code': '000858', 'name': '五粮液', 'current_price': 158.60},
|
||||
{'code': '300750', 'name': '宁德时代', 'current_price': 288.50},
|
||||
]
|
||||
|
||||
df = pd.DataFrame(mock_stocks)
|
||||
|
||||
# 添加其他字段
|
||||
df['change_percent'] = np.random.uniform(-5.0, 5.0, len(df))
|
||||
df['change_amount'] = df['current_price'] * df['change_percent'] / 100
|
||||
df['volume'] = np.random.uniform(1000000, 100000000, len(df))
|
||||
df['amount'] = df['volume'] * df['current_price']
|
||||
df['open'] = df['current_price'] * np.random.uniform(0.98, 1.02, len(df))
|
||||
df['high'] = df['open'] * np.random.uniform(1.0, 1.05, len(df))
|
||||
df['low'] = df['open'] * np.random.uniform(0.95, 1.0, len(df))
|
||||
df['pre_close'] = df['current_price'] - df['change_amount']
|
||||
|
||||
return df
|
||||
|
||||
def filter_by_market(self, df: pd.DataFrame, market: str) -> pd.DataFrame:
|
||||
"""按市场筛选股票
|
||||
|
||||
Args:
|
||||
df: 原始股票列表
|
||||
market: 市场类型,'sh'表示沪市,'sz'表示深市,'cyb'表示创业板,'kc'表示科创板
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 筛选后的结果
|
||||
"""
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
if market == 'sh':
|
||||
# 沪市:6开头
|
||||
filtered = df[df['code'].str.startswith('6')]
|
||||
elif market == 'sz':
|
||||
# 深市主板:0开头且不是002、003
|
||||
filtered = df[df['code'].str.startswith('0') & ~df['code'].str.startswith(('002', '003'))]
|
||||
elif market == 'cyb':
|
||||
# 创业板:3开头
|
||||
filtered = df[df['code'].str.startswith('3')]
|
||||
elif market == 'kc':
|
||||
# 科创板:688开头
|
||||
filtered = df[df['code'].str.startswith('688')]
|
||||
else:
|
||||
# 返回全部
|
||||
filtered = df
|
||||
|
||||
logger.info(f"按市场 [{market}] 筛选后得到 {len(filtered)} 只股票")
|
||||
return filtered
|
||||
|
||||
def save_to_csv(self, df: pd.DataFrame, output_path: str = None) -> str:
|
||||
"""保存数据到CSV文件
|
||||
|
||||
Args:
|
||||
df: 股票数据DataFrame
|
||||
output_path: 输出文件路径,如果为None则自动生成
|
||||
|
||||
Returns:
|
||||
str: 保存的文件路径
|
||||
"""
|
||||
if df.empty:
|
||||
logger.warning("数据为空,跳过保存")
|
||||
return ""
|
||||
|
||||
if output_path is None:
|
||||
# 自动生成输出路径
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
output_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||
'data', 'processed', 'stock_list'
|
||||
)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_path = os.path.join(output_dir, f"a_stock_list_{timestamp}.csv")
|
||||
|
||||
# 保存为CSV
|
||||
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
||||
logger.info(f"A股股票列表已保存到: {output_path}")
|
||||
return output_path
|
||||
|
||||
def save_to_json(self, df: pd.DataFrame, output_path: str = None) -> str:
|
||||
"""保存数据到JSON文件
|
||||
|
||||
Args:
|
||||
df: 股票数据DataFrame
|
||||
output_path: 输出文件路径,如果为None则自动生成
|
||||
|
||||
Returns:
|
||||
str: 保存的文件路径
|
||||
"""
|
||||
if df.empty:
|
||||
logger.warning("数据为空,跳过保存")
|
||||
return ""
|
||||
|
||||
if output_path is None:
|
||||
# 自动生成输出路径
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
output_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||
'data', 'processed', 'stock_list'
|
||||
)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_path = os.path.join(output_dir, f"a_stock_list_{timestamp}.json")
|
||||
|
||||
# 转换为字典并保存
|
||||
data = {
|
||||
'fetch_time': datetime.now().isoformat(),
|
||||
'total_count': len(df),
|
||||
'stocks': df.to_dict(orient='records')
|
||||
}
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
import json
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"A股股票列表已保存到: {output_path}")
|
||||
return output_path
|
||||
|
||||
def main():
|
||||
"""主函数,执行获取A股股票列表"""
|
||||
fetcher = AStockListFetcher()
|
||||
|
||||
# 获取全部A股股票
|
||||
all_stocks = fetcher.get_all_a_stocks()
|
||||
|
||||
if all_stocks.empty:
|
||||
logger.error("获取A股股票列表失败,退出程序")
|
||||
sys.exit(1)
|
||||
|
||||
# 打印统计信息
|
||||
print(f"\n获取成功!共获取 {len(all_stocks)} 只A股股票:")
|
||||
print(f"- 沪市主板: {len(all_stocks[all_stocks['code'].str.startswith('6') & ~all_stocks['code'].str.startswith('688')])}")
|
||||
print(f"- 科创板: {len(all_stocks[all_stocks['code'].str.startswith('688')])}")
|
||||
print(f"- 深市主板: {len(all_stocks[all_stocks['code'].str.startswith('0') & ~all_stocks['code'].str.startswith(('002', '003', '00'))])}")
|
||||
print(f"- 中小板: {len(all_stocks[all_stocks['code'].str.startswith(('002', '003'))])}")
|
||||
print(f"- 创业板: {len(all_stocks[all_stocks['code'].str.startswith('3')])}")
|
||||
|
||||
# 显示前10条数据
|
||||
print("\n前10条数据示例:")
|
||||
print(all_stocks.head(10).to_string(index=False))
|
||||
|
||||
# 保存文件
|
||||
csv_path = fetcher.save_to_csv(all_stocks)
|
||||
json_path = fetcher.save_to_json(all_stocks)
|
||||
|
||||
print(f"\n文件已保存:")
|
||||
print(f"- CSV: {csv_path}")
|
||||
print(f"- JSON: {json_path}")
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user