Files
HKDataManagment/PyCode/ConditionalSelection.py

234 lines
8.8 KiB
Python
Raw Normal View History

2025-08-15 13:22:58 +08:00
from futu import *
import time
import logging
from typing import List, Dict
from MySQLHelper import MySQLHelper # 假设您已有MySQLHelper类
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('Debug.log', encoding='utf-8'), # 关键在这里
logging.StreamHandler()
]
)
class FutuDirectDataImporter:
def __init__(self, db_config: Dict):
self.db_config = db_config
self.table_name = "conditionalselection"
self.quote_ctx = None
def connect_to_futu(self) -> bool:
"""连接Futu API"""
try:
self.quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111)
return True
except Exception as e:
logging.error(f"连接Futu API失败: {str(e)}")
return False
def disconnect(self):
"""断开连接"""
if self.quote_ctx:
self.quote_ctx.close()
def prepare_db_structure(self) -> bool:
"""准备数据库表结构"""
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS `{self.table_name}` (
`stock_code` varchar(255) NOT NULL,
`stock_name` varchar(255) DEFAULT NULL,
`cur_price` float DEFAULT NULL,
`price` float DEFAULT NULL,
`high_price_to_highest_52weeks_ratio` float DEFAULT NULL,
`low_price_to_lowest_52weeks_ratio` float DEFAULT NULL,
`volume` float DEFAULT NULL,
`turnover` float DEFAULT NULL,
`turnover_rate` float DEFAULT NULL,
`change_rate` float DEFAULT NULL,
`amplitude` float DEFAULT NULL,
`pe_ttm` float DEFAULT NULL,
`pb_rate` float DEFAULT NULL,
`market_val` float DEFAULT NULL,
`total_share` float DEFAULT NULL,
`float_share` float DEFAULT NULL,
`float_market_val` float DEFAULT NULL,
`basic_eps` float DEFAULT NULL,
`diluted_eps` float DEFAULT NULL,
UNIQUE KEY `idx_stock_code` (`stock_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""
try:
with MySQLHelper(**self.db_config) as db:
db.execute_update(create_table_sql)
logging.info("数据库表准备就绪")
return True
except Exception as e:
logging.error(f"准备数据库表失败: {str(e)}")
return False
def process_filter_result(self, ret_list: List) -> List[Dict]:
"""处理筛选结果并转换为数据库格式"""
processed_data = []
for item in ret_list:
try:
data = {
'stock_code': item.stock_code,
'stock_name': item.stock_name,
'float_share': item.float_share
}
# data = {
# 'stock_code': item.stock_code,
# 'stock_name': item.stock_name,
# 'cur_price': item.cur_price,
# 'price': item.cur_price,
# 'high_price_to_highest_52weeks_ratio': item.high_price_to_highest_52weeks_ratio,
# 'low_price_to_lowest_52weeks_ratio': item.low_price_to_lowest_52weeks_ratio,
# 'volume': item.volume,
# 'turnover': item.turnover,
# 'turnover_rate': item.turnover_rate,
# 'change_rate': item.change_rate,
# 'amplitude': item.amplitude,
# 'pe_ttm': item.pe_ttm,
# 'pb_rate': item.pb,
# 'market_val': item.market_val,
# 'total_share': item.total_shares,
# 'float_share': item.float_shares,
# 'float_market_val': item.float_market_val,
# 'basic_eps': item.basic_eps,
# 'diluted_eps': item.diluted_eps
# }
# # 计算衍生字段
# if data['cur_price'] and data['high_price_to_highest_52weeks_ratio']:
# data['cur_price_to_highest_52weeks_ratio'] = (
# data['cur_price'] / data['high_price_to_highest_52weeks_ratio']
# )
# if data['cur_price'] and data['low_price_to_lowest_52weeks_ratio']:
# data['cur_price_to_lowest_52weeks_ratio'] = (
# data['cur_price'] / data['low_price_to_lowest_52weeks_ratio']
# )
processed_data.append(data)
except Exception as e:
logging.error(f"处理股票 {getattr(item, 'stock_code', '未知')} 数据失败: {str(e)}")
return processed_data
def save_batch_to_database(self, data_batch: List[Dict]) -> int:
"""批量保存数据到数据库"""
if not data_batch:
return 0
# 准备SQL
columns = [
'stock_code', 'stock_name', 'cur_price', 'price',
'high_price_to_highest_52weeks_ratio', 'low_price_to_lowest_52weeks_ratio',
'volume', 'turnover', 'turnover_rate', 'change_rate', 'amplitude',
'pe_ttm', 'pb_rate', 'market_val', 'total_share', 'float_share',
'float_market_val', 'basic_eps', 'diluted_eps',
'cur_price_to_highest_52weeks_ratio', 'cur_price_to_lowest_52weeks_ratio'
]
placeholders = ', '.join(['%s'] * len(columns))
columns_str = ', '.join([f'`{col}`' for col in columns])
updates = ', '.join([f'`{col}`=VALUES(`{col}`)' for col in columns if col != 'stock_code'])
sql = f"""
INSERT INTO `{self.table_name}` ({columns_str})
VALUES ({placeholders})
ON DUPLICATE KEY UPDATE {updates}
"""
try:
with MySQLHelper(**self.db_config) as db:
# 准备参数列表
params = []
for item in data_batch:
params.append(tuple(item.get(col) for col in columns))
affected_rows = db.execute_many(sql, params)
logging.info(f"批量保存了 {len(data_batch)} 条记录,影响 {affected_rows}")
return affected_rows
except Exception as e:
logging.error(f"批量保存失败: {str(e)}")
return 0
def run_direct_import(self):
"""运行直接导入流程"""
if not self.connect_to_futu():
return False
if not self.prepare_db_structure():
self.disconnect()
return False
simple_filter = SimpleFilter()
simple_filter.filter_min = 0
simple_filter.filter_max = 100000000000000
simple_filter.stock_field = StockField.FLOAT_SHARE
simple_filter.is_no_filter = False
# simple_filter1 = SimpleFilter()
# simple_filter1.filter_min = 0
# simple_filter1.filter_max = 100000000000000000000
# simple_filter1.stock_field = StockField.CUR_PRICE
# simple_filter1.is_no_filter = False
nBegin = 0
last_page = False
total_imported = 0
try:
while not last_page:
ret, ls = self.quote_ctx.get_stock_filter(
market=Market.HK,
filter_list=[simple_filter],
begin=nBegin
)
if ret == RET_OK:
last_page, all_count, ret_list = ls
logging.info(f'获取股票列表进度: {nBegin}/{all_count}')
# 处理并保存当前批次数据
processed_data = self.process_filter_result(ret_list)
if processed_data:
affected_rows = self.save_batch_to_database(processed_data)
total_imported += affected_rows
nBegin += len(ret_list)
else:
logging.error(f'获取股票列表错误: {ls}')
break
time.sleep(3) # 避免触发限频
logging.info(f"导入完成! 共导入 {total_imported} 条记录")
return total_imported > 0
except Exception as e:
logging.error(f"导入过程中发生错误: {str(e)}")
return False
finally:
self.disconnect()
# 使用示例
if __name__ == "__main__":
# 数据库配置
db_config = {
'host': 'localhost',
'user': 'root',
'password': 'bzskmysql',
'database': 'fullmarketdata_hk'
}
# 创建导入器并运行
importer = FutuDirectDataImporter(db_config)
importer.run_direct_import()