updata updatekline.py

This commit is contained in:
2025-08-20 14:09:35 +08:00
parent 3be41434e3
commit 786c669d62
12 changed files with 1137 additions and 957 deletions

View File

@@ -1,298 +0,0 @@
"""
计算平均市值
根据 conditionalselection 表格中的股票代码查找日K数据表格
每个月计算一次结果存放在 hk_monthly_avg_2410_2508 表格中
"""
import pandas as pd
from datetime import datetime
import logging
from futu import *
from pymysql import Error
from MySQLHelper import MySQLHelper # MySQLHelper类保存为单独文件
from typing import Optional, List, Dict, Union, Tuple
def create_monthly_avg_table(db_config: dict, target_table: str = "monthly_close_avg") -> bool:
"""
创建专门存储2024年10月至2025年8月月度均值的表结构
Args:
db_config: 数据库配置
target_table: 目标表名
Returns:
bool: 是否创建成功
"""
try:
with MySQLHelper(**db_config) as db:
create_sql = f"""
CREATE TABLE IF NOT EXISTS {target_table} (
id INT AUTO_INCREMENT PRIMARY KEY,
stock_code VARCHAR(20) NOT NULL COMMENT '股票代码',
stock_name VARCHAR(50) COMMENT '股票名称',
ym_2410 DECIMAL(10, 3) COMMENT '2024年10月均收盘价',
ym_2411 DECIMAL(10, 3) COMMENT '2024年11月均收盘价',
ym_2412 DECIMAL(10, 3) COMMENT '2024年12月均收盘价',
ym_2501 DECIMAL(10, 3) COMMENT '2025年1月均收盘价',
ym_2502 DECIMAL(10, 3) COMMENT '2025年2月均收盘价',
ym_2503 DECIMAL(10, 3) COMMENT '2025年3月均收盘价',
ym_2504 DECIMAL(10, 3) COMMENT '2025年4月均收盘价',
ym_2505 DECIMAL(10, 3) COMMENT '2025年5月均收盘价',
ym_2506 DECIMAL(10, 3) COMMENT '2025年6月均收盘价',
ym_2507 DECIMAL(10, 3) COMMENT '2025年7月均收盘价',
ym_2508 DECIMAL(10, 3) COMMENT '2025年8月均收盘价',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_stock_code (stock_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度收盘价均值表(2024.10-2025.08)'
"""
db.execute_update(create_sql)
# logging.info(f"创建/确认表 {target_table} 结构成功")
return True
except Exception as e:
logging.error(f"创建表失败: {str(e)}")
return False
def calculate_and_save_monthly_avg(db_config: dict,
source_table: str = "stock_quotes",
target_table: str = "monthly_close_avg") -> bool:
"""
计算并保存2024年10月至2025年8月的月度收盘价均值
Args:
db_config: 数据库配置
source_table: 源数据表名
target_table: 目标表名
Returns:
bool: 是否成功
"""
# 定义分析的时间范围
month_ranges = {
'ym_2410': ('2024-10-01', '2024-10-31'),
'ym_2411': ('2024-11-01', '2024-11-30'),
'ym_2412': ('2024-12-01', '2024-12-31'),
'ym_2501': ('2025-01-01', '2025-01-31'),
'ym_2502': ('2025-02-01', '2025-02-28'),
'ym_2503': ('2025-03-01', '2025-03-31'),
'ym_2504': ('2025-04-01', '2025-04-30'),
'ym_2505': ('2025-05-01', '2025-05-31'),
'ym_2506': ('2025-06-01', '2025-06-30'),
'ym_2507': ('2025-07-01', '2025-07-31'),
'ym_2508': ('2025-08-01', '2025-08-31')
}
try:
# 确保表结构存在
if not create_monthly_avg_table(db_config, target_table):
return False
with MySQLHelper(**db_config) as db:
# 获取所有股票代码和名称
stock_info = db.execute_query(
f"SELECT DISTINCT stock_code, stock_name FROM {source_table}"
)
if not stock_info:
logging.error("没有获取到股票基本信息")
return False
# 为每只股票计算各月均值
for stock in stock_info:
stock_code = stock['stock_code']
stock_name = stock['stock_name']
monthly_data = {'stock_code': stock_code, 'stock_name': stock_name}
# 计算每个月的均值
for month_col, (start_date, end_date) in month_ranges.items():
sql = f"""
SELECT AVG(close_price) as avg_close
FROM {source_table}
WHERE stock_code = %s
AND trade_date BETWEEN %s AND %s
"""
result = db.execute_query(sql, (stock_code, start_date, end_date))
monthly_data[month_col] = float(result[0]['avg_close']) if result and result[0]['avg_close'] else None
# 插入或更新数据
upsert_sql = f"""
INSERT INTO {target_table} (
stock_code, stock_name,
ym_2410, ym_2411, ym_2412,
ym_2501, ym_2502, ym_2503, ym_2504,
ym_2505, ym_2506, ym_2507, ym_2508
) VALUES (
%(stock_code)s, %(stock_name)s,
%(ym_2410)s, %(ym_2411)s, %(ym_2412)s,
%(ym_2501)s, %(ym_2502)s, %(ym_2503)s, %(ym_2504)s,
%(ym_2505)s, %(ym_2506)s, %(ym_2507)s, %(ym_2508)s
)
ON DUPLICATE KEY UPDATE
stock_name = VALUES(stock_name),
ym_2410 = VALUES(ym_2410),
ym_2411 = VALUES(ym_2411),
ym_2412 = VALUES(ym_2412),
ym_2501 = VALUES(ym_2501),
ym_2502 = VALUES(ym_2502),
ym_2503 = VALUES(ym_2503),
ym_2504 = VALUES(ym_2504),
ym_2505 = VALUES(ym_2505),
ym_2506 = VALUES(ym_2506),
ym_2507 = VALUES(ym_2507),
ym_2508 = VALUES(ym_2508),
update_time = CURRENT_TIMESTAMP
"""
db.execute_update(upsert_sql, monthly_data)
logging.info("月度均值计算和保存完成")
return True
except Exception as e:
logging.error(f"计算和保存月度均值失败: {str(e)}")
return False
# 安全转换函数
def safe_float(v) -> Optional[float]:
"""安全转换为float处理N/A和空值"""
try:
return float(v) if pd.notna(v) and str(v).upper() != 'N/A' else None
except (ValueError, TypeError):
return None
def safe_int(v) -> Optional[int]:
"""安全转换为int处理N/A和空值"""
try:
return int(v) if pd.notna(v) and str(v).upper() != 'N/A' else None
except (ValueError, TypeError):
return None
def safe_parse_date(date_str, date_format='%Y-%m-%d'):
"""
安全解析日期字符串
:param date_str: 日期字符串
:param date_format: 日期格式
:return: 解析后的datetime对象或None
"""
if not date_str or pd.isna(date_str) or str(date_str).strip() == '':
return None
try:
return datetime.strptime(str(date_str), date_format)
except ValueError:
logging.warning(f"无法解析日期字符串: {date_str}")
return None
def validate_market_data(dataset: list) -> list:
"""
验证市场数据有效性
Args:
dataset (list): 原始数据集
Returns:
list: 通过验证的数据集
"""
validated_data = []
for item in dataset:
try:
# 必要字段检查
if not item.get('code') or not item.get('name'):
logging.warning(f"跳过无效数据: 缺少必要字段 code或name")
continue
# 筛选股票名称
if item.get('name')[-1] == 'R':
continue
# 数值范围验证
if item.get('lot_size') is not None and item['lot_size'] < 0:
logging.warning(f"股票 {item['code']} 的lot_size为负值: {item['lot_size']}")
item['lot_size'] = None
validated_data.append(item)
except Exception as e:
logging.warning(f"数据验证失败,跳过记录 {item.get('code')}: {str(e)}")
continue
return validated_data
def get_market_data(market: Market) -> List[str]:
"""
从Futu API获取指定市场的股票代码列表
Args:
market (Market): 市场枚举值,如 Market.SH, Market.SZ
Returns:
List[str]: 股票代码列表
"""
quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111)
try:
ret, data = quote_ctx.get_stock_basicinfo(market, SecurityType.STOCK)
if ret == RET_OK:
# 提取code列并转换为列表
codes = data['code'].astype(str).tolist()
logging.info(f"获取到 {market} 市场 {len(codes)} 个股票代码")
return codes
else:
logging.error(f"获取股票代码失败: {data}")
return []
except Exception as e:
logging.error(f"获取股票代码时发生异常: {str(e)}")
return []
finally:
quote_ctx.close()
def get_stock_codes() -> List[str]:
"""从conditionalselection表获取所有股票代码"""
try:
with MySQLHelper(**db_config) as db:
sql = f"SELECT DISTINCT stock_code,stock_name FROM conditionalselection"
results = db.execute_query(sql)
return [
row['stock_code']
for row in results
if row['stock_code'] and (row.get('stock_name', '') and row['stock_name'][-1] != 'R') # 排除 name 以 R 结尾的票
]
except Exception as e:
logging.error(f"获取股票代码失败: {str(e)}")
return []
if __name__ == "__main__":
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('Debug.log', encoding='utf-8'), # 关键在这里
logging.StreamHandler()
]
)
# 数据库配置
db_config = {
'host': 'localhost',
'user': 'root',
'password': 'bzskmysql',
'database': 'klinedata_1d_hk'
}
# market_data = get_market_data(Market.HK)
market_data = get_stock_codes() # 使用按照价格和流通股数量筛选的那个表格
for code in market_data:
tablename = 'hk_' + code[3:]
# 计算并保存月度均值
success = calculate_and_save_monthly_avg(
db_config=db_config,
source_table=tablename,
target_table="hk_monthly_avg_2410_2508"
)
if success:
logging.info("月度均值处理成功完成")
else:
logging.error("处理过程中出现错误")

View File

@@ -1,16 +1,17 @@
"""
#
# 使用筛选器来更新股票列表
# 使用筛选器来更新股票列表 <临时使用,功能是完整的,后面继续优化>
#
"""
from futu import *
import time
import logging
from typing import List, Dict
from MySQLHelper import MySQLHelper # 假设您已有MySQLHelper类
from LogHelper import LogHelper
# 基本用法(自动创建日期日志+控制台输出)
logger = LogHelper().setup()
logger = LogHelper(logger_name = 'floatShare').setup()
class FutuStockFilter:
def __init__(self, db_config:dict):
@@ -24,7 +25,7 @@ class FutuStockFilter:
self.quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111)
return True
except Exception as e:
logging.error(f"连接Futu API失败: {str(e)}")
logger.error(f"连接Futu API失败: {str(e)}")
return False
def disconnect(self):
@@ -48,10 +49,10 @@ class FutuStockFilter:
try:
with MySQLHelper(**self.db_config) as db:
db.execute_update(create_table_sql)
logging.info("数据库表准备就绪")
logger.info("数据库表准备就绪")
return True
except Exception as e:
logging.error(f"准备数据库表失败: {str(e)}")
logger.error(f"准备数据库表失败: {str(e)}")
return False
def process_filter_result(self, ret_list: List) -> List[Dict]:
@@ -66,7 +67,7 @@ class FutuStockFilter:
}
processed_data.append(data)
except Exception as e:
logging.error(f"处理股票 {getattr(item, 'stock_code', '未知')} 数据失败: {str(e)}")
logger.error(f"处理股票 {getattr(item, 'stock_code', '未知')} 数据失败: {str(e)}")
return processed_data
@@ -98,10 +99,10 @@ class FutuStockFilter:
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}")
logger.info(f"批量保存了 {len(data_batch)} 条记录,影响 {affected_rows}")
return affected_rows
except Exception as e:
logging.error(f"批量保存失败: {str(e)}")
logger.error(f"批量保存失败: {str(e)}")
return 0
def run_direct_import(self):
@@ -113,7 +114,7 @@ class FutuStockFilter:
self.disconnect()
return False
logging.info(f"================= start ===================")
logger.info(f"================= start ===================")
simple_filter = SimpleFilter()
simple_filter.filter_min = 0
simple_filter.filter_max = 100000000000000
@@ -134,7 +135,7 @@ class FutuStockFilter:
if ret == RET_OK:
last_page, all_count, ret_list = ls
logging.info(f'获取股票列表进度: {nBegin}/{all_count}')
logger.info(f'获取股票列表进度: {nBegin}/{all_count}')
# 处理并保存当前批次数据
processed_data = self.process_filter_result(ret_list)
@@ -144,17 +145,17 @@ class FutuStockFilter:
nBegin += len(ret_list)
else:
logging.error(f'获取股票列表错误: {ls}')
logger.error(f'获取股票列表错误: {ls}')
break
time.sleep(3) # 避免触发限频
logging.info(f"导入完成! 共导入 {total_imported} 条记录")
logging.info(f"================= end ===================")
logger.info(f"导入完成! 共导入 {total_imported} 条记录")
logger.info(f"================= end ===================")
return total_imported > 0
except Exception as e:
logging.error(f"导入过程中发生错误: {str(e)}")
logger.error(f"导入过程中发生错误: {str(e)}")
return False
finally:
self.disconnect()

View File

@@ -1,315 +0,0 @@
import csv
import pandas as pd
from MySQLHelper import MySQLHelper
import logging
from typing import List, Dict, Optional, Tuple
from datetime import datetime
def get_monthly_avg_data(db_config: dict, table_name: str) -> Optional[List[Dict]]:
"""
从数据库读取月度均值数据
Args:
db_config: 数据库配置
table_name: 源数据表名
Returns:
List[Dict]: 查询结果数据集失败返回None
"""
try:
with MySQLHelper(**db_config) as db:
# 获取表结构信息
columns = db.execute_query(f"""
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
ORDER BY ORDINAL_POSITION
""", (db_config['database'], table_name))
if not columns:
logging.error(f"{table_name} 不存在或没有列")
return None
# 获取列名列表(排除id和update_time)
field_names = [col['COLUMN_NAME'] for col in columns
if col['COLUMN_NAME'] not in ('id', 'update_time')]
# 查询数据
data = db.execute_query(f"""
SELECT {', '.join(field_names)}
FROM {table_name}
ORDER BY stock_code
""")
if not data:
logging.error(f"{table_name} 中没有数据")
return None
return data
except Exception as e:
logging.error(f"从数据库读取月度均值数据失败: {str(e)}")
return None
def get_float_share_data(db_config: dict, table_name: str) -> Optional[List[Dict]]:
"""
从conditionalselection表读取流通股本数据
Args:
db_config: 数据库配置
table_name: 源数据表名
Returns:
List[Dict]: 查询结果数据集失败返回None
"""
try:
with MySQLHelper(**db_config) as db:
# 查询流通股本数据
data = db.execute_query(f"""
SELECT stock_code, stock_name, float_share
FROM {table_name}
ORDER BY stock_code
""")
if not data:
logging.error(f"{table_name} 中没有流通股本数据")
return None
return data
except Exception as e:
logging.error(f"从数据库读取流通股本数据失败: {str(e)}")
return None
def merge_data(monthly_data: List[Dict], float_share_data: List[Dict]) -> List[Dict]:
"""
合并月度均价数据和流通股本数据
Args:
monthly_data: 月度均价数据
float_share_data: 流通股本数据
Returns:
List[Dict]: 合并后的数据集
"""
merged_data = []
float_share_dict = {item['stock_code']: item['float_share'] for item in float_share_data}
for item in monthly_data:
merged_item = item.copy()
merged_item['float_share'] = float_share_dict.get(item['stock_code'], 'N/A')
merged_data.append(merged_item)
return merged_data
def export_to_csv(data: List[Dict], output_file: str) -> bool:
"""
将合并后的数据导出到CSV文件
Args:
data: 要导出的数据集
output_file: 输出的CSV文件路径
Returns:
bool: 是否导出成功
"""
if not data:
return False
try:
# 获取字段名(使用第一个数据的键)
field_names = list(data[0].keys())
# 字段名到中文的映射
header_map = {
'stock_code': '股票代码',
'stock_name': '股票名称',
'float_share': '流通股本(千股)',
'ym_2410': '2024年10月均收盘价',
'ym_2411': '2024年11月均收盘价',
'ym_2412': '2024年12月均收盘价',
'ym_2501': '2025年1月均收盘价',
'ym_2502': '2025年2月均收盘价',
'ym_2503': '2025年3月均收盘价',
'ym_2504': '2025年4月均收盘价',
'ym_2505': '2025年5月均收盘价',
'ym_2506': '2025年6月均收盘价',
'ym_2507': '2025年7月均收盘价',
'ym_2508': '2025年8月均收盘价'
}
with open(output_file, mode='w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=field_names)
# 写入中文表头
writer.writerow({col: header_map.get(col, col) for col in field_names})
# 写入数据
writer.writerows(data)
logging.info(f"成功导出 {len(data)} 条记录到CSV文件: {output_file}")
return True
except Exception as e:
logging.error(f"导出到CSV失败: {str(e)}")
return False
def export_to_excel(data: List[Dict], output_file: str) -> bool:
"""
将合并后的数据导出为Excel文件(包含多个工作表)
Args:
data: 要导出的数据集
output_file: 输出的Excel文件路径
Returns:
bool: 是否导出成功
"""
if not data:
return False
try:
# 转换为DataFrame
df = pd.DataFrame(data)
# 设置股票代码为索引
if 'stock_code' in df.columns:
df.set_index('stock_code', inplace=True)
# 创建Excel writer对象
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
# 1. 原始数据工作表
df.to_excel(writer, sheet_name='合并数据')
# 2. 统计信息工作表(仅当有数值列时)
numeric_cols = [col for col in df.columns if col.startswith('ym_') and pd.api.types.is_numeric_dtype(df[col])]
if numeric_cols:
try:
stats = df[numeric_cols].describe().loc[['mean', 'min', 'max', 'std']]
stats.to_excel(writer, sheet_name='统计信息')
except KeyError:
logging.warning("无法生成完整的统计信息,数据可能不足")
# 生成简化版统计信息
stats = df[numeric_cols].agg(['mean', 'min', 'max', 'std'])
stats.to_excel(writer, sheet_name='统计信息')
# 3. 涨幅排名工作表(需要至少两个月份数据)
if len(numeric_cols) >= 2:
first_month = numeric_cols[0]
last_month = numeric_cols[-1]
try:
df['涨幅(%)'] = (df[last_month] - df[first_month]) / df[first_month] * 100
result_df = df[['stock_name', '涨幅(%)', 'float_share']].copy()
result_df.dropna(subset=['涨幅(%)'], inplace=True)
result_df.sort_values('涨幅(%)', ascending=False, inplace=True)
result_df.to_excel(writer, sheet_name='涨幅排名')
except Exception as e:
logging.warning(f"无法计算涨幅: {str(e)}")
# 4. 月度趋势工作表
if numeric_cols:
try:
trend_df = df[numeric_cols].transpose()
trend_df.index = [col.replace('ym_', '') for col in numeric_cols]
trend_df.to_excel(writer, sheet_name='月度趋势')
except Exception as e:
logging.warning(f"无法生成月度趋势: {str(e)}")
# 5. 流通股本分析工作表
if 'float_share' in df.columns and pd.api.types.is_numeric_dtype(df['float_share']):
try:
float_stats = df['float_share'].describe().to_frame().T
float_stats.to_excel(writer, sheet_name='流通股本分析')
except Exception as e:
logging.warning(f"无法生成流通股本分析: {str(e)}")
logging.info(f"成功导出Excel文件: {output_file}")
return True
except Exception as e:
logging.error(f"导出Excel失败: {str(e)}")
return False
def export_combined_data(db_config: dict,
monthly_table: str,
float_share_table: str,
csv_file: str = None,
excel_file: str = None) -> bool:
"""
导出合并后的数据到CSV和/或Excel
Args:
db_config: 数据库配置
monthly_table: 月度均价表名
float_share_table: 流通股本表名
csv_file: CSV输出路径(可选)
excel_file: Excel输出路径(可选)
Returns:
bool: 是否至少有一种格式导出成功
"""
if not csv_file and not excel_file:
logging.error("必须指定至少一种输出格式")
return False
# 从数据库获取数据
monthly_data = get_monthly_avg_data(db_config, monthly_table)
if not monthly_data:
logging.error("无法获取月度均价数据")
return False
float_share_data = get_float_share_data(db_config, float_share_table)
if not float_share_data:
logging.error("无法获取流通股本数据")
return False
# 合并数据
merged_data = merge_data(monthly_data, float_share_data)
# 导出结果
csv_success = True
excel_success = True
if csv_file:
csv_success = export_to_csv(merged_data, csv_file)
if excel_file:
excel_success = export_to_excel(merged_data, excel_file)
return csv_success or excel_success
# 主程序入口
if __name__ == "__main__":
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('export_combined_data.log', encoding='utf-8'),
logging.StreamHandler()
]
)
# 数据库配置
db_config = {
'host': 'localhost',
'user': 'root',
'password': 'bzskmysql',
'database': 'klinedata_1d_hk_akshare'
}
# 导出合并数据
success = export_combined_data(
db_config=db_config,
monthly_table="hk_monthly_avg_2410_2508",
float_share_table="conditionalselection",
csv_file="hk_stocks_combined_data.csv",
excel_file="hk_stocks_combined_data.xlsx"
)
if success:
logging.info("数据合并导出成功完成")
else:
logging.error("数据合并导出过程中出现错误")

View File

@@ -1,47 +1,9 @@
# import logging
# import sys
# class LogHelper:
# def __init__(self,
# level=logging.INFO,
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
# handlers=None):
# """
# 初始化日志配置
# :param level: 日志级别,默认为 logging.INFO
# :param format: 日志格式字符串
# :param handlers: 日志处理器列表,默认为空(会自动添加控制台处理器)
# """
# self.level = level
# self.format = format
# self.handlers = handlers if handlers is not None else []
# def add_console_handler(self, stream=sys.stdout, encoding='utf-8'):
# """添加控制台处理器"""
# console_handler = logging.StreamHandler(stream)
# console_handler.setFormatter(logging.Formatter(self.format))
# # 设置控制台编码
# if encoding:
# console_handler.encoding = encoding
# self.handlers.append(console_handler)
# def add_file_handler(self, filename, encoding='utf-8'):
# """添加文件处理器(解决中文乱码问题)"""
# # 使用支持UTF-8编码的文件处理器
# filepath = "log/" + filename
# file_handler = logging.FileHandler(filepath, encoding=encoding)
# file_handler.setFormatter(logging.Formatter(self.format))
# self.handlers.append(file_handler)
# def setup(self):
# """应用日志配置"""
# logging.basicConfig(
# level=self.level,
# format=self.format,
# handlers=self.handlers
# )
"""
日志管理类
日志输出到运行目录指定文件夹
日志文件按照YYYY-MM-dd.log 格式输出
"""
import logging
import sys
@@ -49,11 +11,21 @@ import os
from datetime import datetime
class LogHelper:
# 内部日志级别映射表
_LEVEL_MAP = {
'DEBUG': 10,
'INFO': 20,
'WARNING': 30,
'ERROR': 40,
'CRITICAL': 50
}
def __init__(self,
level=logging.INFO,
level='INFO',
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=None,
log_dir="logs"):
log_dir="logs",
logger_name=None):
"""
初始化日志配置
@@ -62,10 +34,11 @@ class LogHelper:
:param handlers: 日志处理器列表,默认为空(会自动添加控制台处理器)
:param log_dir: 日志存储目录,默认为"logs"
"""
self.level = level
self.level = self._LEVEL_MAP.get(level.upper(), 20) # 默认INFO
self.format = format
self.handlers = handlers if handlers is not None else []
self.log_dir = log_dir
self.logger_name = logger_name
# 确保日志目录存在
os.makedirs(self.log_dir, exist_ok=True)
@@ -76,6 +49,7 @@ class LogHelper:
if encoding:
console_handler.encoding = encoding
self.handlers.append(console_handler)
return self # 支持链式调用
def _get_daily_log_path(self):
"""生成基于当前日期的日志文件路径"""
@@ -89,6 +63,7 @@ class LogHelper:
file_handler = logging.FileHandler(log_path, encoding=encoding)
file_handler.setFormatter(logging.Formatter(self.format))
self.handlers.append(file_handler)
return self # 支持链式调用
def setup(self):
"""应用日志配置(自动添加日期文件处理器)"""
@@ -97,9 +72,19 @@ class LogHelper:
self.add_console_handler()
self._add_daily_file_handler()
# 应用配置
logging.basicConfig(
level=self.level,
format=self.format,
handlers=self.handlers
)
# 获取或创建logger
logger = logging.getLogger(self.logger_name)
logger.setLevel(self.level)
# 移除所有现有处理器(避免重复添加)
for handler in logger.handlers[:]:
logger.removeHandler(handler)
# 添加配置的处理器
for handler in self.handlers:
logger.addHandler(handler)
# 确保日志消息不会传递给父logger避免重复记录
logger.propagate = False
return logger

View File

@@ -1,6 +1,25 @@
"""
MySqlHelper 增强版
—— 增加事务管理
—— 增加ID获取
—— 增加表操作等使用功能
"""
import pymysql
from pymysql import Error
from typing import List, Dict, Union, Optional, Tuple
from contextlib import contextmanager
from LogHelper import LogHelper
# 基本用法(自动创建日期日志+控制台输出)
logger = LogHelper(logger_name = 'database').setup()
# # 高级用法(自定义配置)
# logger = LogHelper(
# level=logging.DEBUG,
# log_dir="databaselogs",
# format='%(levelname)s - %(message)s'
# ).setup()
class MySQLHelper:
def __init__(self, host: str, user: str, password: str, database: str,
@@ -39,10 +58,10 @@ class MySQLHelper:
cursorclass=pymysql.cursors.DictCursor # 返回字典形式的结果
)
self.cursor = self.connection.cursor()
print("MySQL数据库连接成功")
logger.info("MySQL数据库连接成功")
return True
except Error as e:
print(f"连接MySQL数据库失败: {e}")
logger.error(f"连接MySQL数据库失败: {e}")
return False
def close(self) -> None:
@@ -53,7 +72,7 @@ class MySQLHelper:
self.cursor.close()
if self.connection:
self.connection.close()
print("MySQL数据库连接已关闭")
logger.info("MySQL数据库连接已关闭")
def execute_query(self, sql: str, params: Union[Tuple, List, Dict, None] = None) -> List[Dict]:
"""
@@ -66,7 +85,7 @@ class MySQLHelper:
self.cursor.execute(sql, params)
return self.cursor.fetchall()
except Error as e:
print(f"查询执行失败: {e}")
logger.error(f"查询执行失败: {e}")
return []
def execute_update(self, sql: str, params: Union[Tuple, List, Dict, None] = None) -> int:
@@ -82,7 +101,7 @@ class MySQLHelper:
return affected_rows
except Error as e:
self.connection.rollback()
print(f"更新执行失败: {e}")
logger.error(f"更新执行失败: {e}")
return 0
def execute_many(self, sql: str, params_list: List[Union[Tuple, List, Dict]]) -> int:
@@ -98,9 +117,41 @@ class MySQLHelper:
return affected_rows
except Error as e:
self.connection.rollback()
print(f"批量执行失败: {e}")
logger.error(f"批量执行失败: {e}")
return 0
# ================== 新增功能方法 ==================
def get_last_insert_id(self) -> int:
"""
获取最后插入行的自增ID
:return: 自增ID值
"""
return self.cursor.lastrowid if self.cursor else 0
def execute_insert(self, sql: str, params: Union[Tuple, List, Dict, None] = None) -> int:
"""
执行插入操作并返回自增ID
:return: 自增ID值
"""
self.execute_update(sql, params)
return self.get_last_insert_id()
@contextmanager
def transaction(self):
"""
事务上下文管理器,确保操作原子性
用法:
with db.transaction():
db.execute_update(...)
db.execute_many(...)
"""
try:
yield
self.connection.commit()
except Exception as e:
self.connection.rollback()
raise e
def get_one(self, sql: str, params: Union[Tuple, List, Dict, None] = None) -> Optional[Dict]:
"""
获取单条记录
@@ -112,7 +163,7 @@ class MySQLHelper:
self.cursor.execute(sql, params)
return self.cursor.fetchone()
except Error as e:
print(f"获取单条记录失败: {e}")
logger.error(f"获取单条记录失败: {e}")
return None
def table_exists(self, table_name: str) -> bool:
@@ -124,12 +175,61 @@ class MySQLHelper:
sql = "SHOW TABLES LIKE %s"
result = self.execute_query(sql, (table_name,))
return len(result) > 0
def create_table(self, sql: str) -> bool:
"""
执行建表语句
:param sql: CREATE TABLE语句
:return: 是否成功
"""
try:
self.cursor.execute(sql)
self.connection.commit()
return True
except Error as e:
logger.error(f"创建表失败: {e}")
return False
def drop_table(self, table_name: str) -> bool:
"""
删除表
:param table_name: 表名
:return: 是否成功
"""
try:
self.cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
self.connection.commit()
return True
except Error as e:
logger.error(f"删除表失败: {e}")
return False
def get_columns(self, table_name: str) -> List[Dict]:
"""
获取表的列信息
:param table_name: 表名
:return: 列信息字典列表
"""
return self.execute_query(f"DESCRIBE {table_name}")
# ================== 事务控制方法 ==================
def start_transaction(self):
"""显式开始事务"""
self.connection.begin()
def commit(self):
"""提交事务"""
self.connection.commit()
def rollback(self):
"""回滚事务"""
self.connection.rollback()
# ================== 上下文管理器 ==================
def __enter__(self):
"""支持with上下文管理"""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""支持with上下文管理"""
self.close()

View File

@@ -1,34 +1,29 @@
"""
检查K线数据是否下载完成
检测表格中的股票是否都有对应的表格
"""
import logging
from typing import List
from MySQLHelper import MySQLHelper # 假设您已有MySQLHelper类
from LogHelper import LogHelper
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('Debug.log', encoding='utf-8'), # 关键在这里
logging.StreamHandler()
]
)
# 基本用法(自动创建日期日志+控制台输出)
logger = LogHelper(logger_name = 'checkTable').setup()
class StockTableChecker:
def __init__(self, db_config: dict):
self.db_config = db_config
self.conditional_selection_table = "conditionalselection"
self.stock_list_table = "stock_filter"
def get_stock_codes(self) -> List[str]:
"""从conditionalselection表获取所有股票代码"""
try:
with MySQLHelper(**self.db_config) as db:
sql = f"SELECT DISTINCT stock_code FROM {self.conditional_selection_table}"
sql = f"SELECT DISTINCT stock_code FROM {self.stock_list_table}"
results = db.execute_query(sql)
return [row['stock_code'] for row in results if row['stock_code']]
except Exception as e:
logging.error(f"获取股票代码失败: {str(e)}")
logger.error(f"获取股票代码失败: {str(e)}")
return []
def check_tables_exist(self, stock_codes: List[str]) -> dict:
@@ -57,18 +52,18 @@ class StockTableChecker:
return result
except Exception as e:
logging.error(f"检查表存在性失败: {str(e)}")
logger.error(f"检查表存在性失败: {str(e)}")
return {'exists': [], 'not_exists': []}
def write_missing_codes_to_txt(self, missing_codes: list, filename: str = "missing_tables.txt"):
def write_missing_codes_to_txt(self, missing_codes: list, filename: str = "data\missing_tables.txt"):
"""将缺失的股票代码写入TXT文件"""
try:
with open(filename, 'w', encoding='utf-8') as f:
for code in missing_codes:
f.write(f"{code}\n")
logging.info(f"已将 {len(missing_codes)} 个缺失表对应的股票代码写入 {filename}")
logger.info(f"已将 {len(missing_codes)} 个缺失表对应的股票代码写入 {filename}")
except Exception as e:
logging.error(f"写入TXT文件失败: {str(e)}")
logger.error(f"写入TXT文件失败: {str(e)}")
def read_missing_codes_basic(file_path='missing_tables.txt'):
"""基础读取方法 - 按行读取所有内容"""
@@ -88,15 +83,15 @@ class StockTableChecker:
def run_check(self):
"""执行完整的检查流程"""
logging.info("开始检查股票代码对应表...")
logger.info("开始检查股票代码对应表...")
# 1. 获取所有股票代码
stock_codes = self.get_stock_codes()
if not stock_codes:
logging.error("没有获取到任何股票代码")
logger.error("没有获取到任何股票代码")
return
logging.info(f"共获取到 {len(stock_codes)} 个股票代码")
logger.info(f"共获取到 {len(stock_codes)} 个股票代码")
# 2. 检查表存在性
check_result = self.check_tables_exist(stock_codes)
@@ -104,20 +99,20 @@ class StockTableChecker:
not_exists_count = len(check_result['not_exists'])
# 3. 输出结果
logging.info("\n检查结果:")
logging.info(f"存在的表数量: {exists_count}")
logging.info(f"不存在的表数量: {not_exists_count}")
logger.info("\n检查结果:")
logger.info(f"存在的表数量: {exists_count}")
logger.info(f"不存在的表数量: {not_exists_count}")
if not_exists_count > 0:
logging.info("\n不存在的表对应的股票代码:")
logger.info("\n不存在的表对应的股票代码:")
for code in check_result['not_exists']:
logging.info(code)
logger.info(code)
# 4. 统计信息
logging.info("\n统计摘要:")
logging.info(f"总股票代码数: {len(stock_codes)}")
logging.info(f"存在对应表的比例: {exists_count/len(stock_codes):.2%}")
logging.info(f"缺失对应表的比例: {not_exists_count/len(stock_codes):.2%}")
logger.info("\n统计摘要:")
logger.info(f"总股票代码数: {len(stock_codes)}")
logger.info(f"存在对应表的比例: {exists_count/len(stock_codes):.2%}")
logger.info(f"缺失对应表的比例: {not_exists_count/len(stock_codes):.2%}")
# 4. 将缺失的股票代码写入TXT文件
if check_result['not_exists']:
@@ -131,7 +126,7 @@ if __name__ == "__main__":
'host': 'localhost',
'user': 'root',
'password': 'bzskmysql',
'database': 'klinedata_1d_hk'
'database': 'hk_kline_1d'
}
# 创建检查器并运行

View File

@@ -10,18 +10,20 @@
—— 根据股票代码,获取股票历史数据
—— 根据股票代码,获取流通股数量
—— 根据股票代码,新建/更新数据表
—— 更新完成需检查 ,数据是否完整 -> checktable.py 操作界面做好了之后,再融合到整体代码中
"""
from futu import *
from pymysql import Error
from MySQLHelper import MySQLHelper # MySQLHelper类保存为单独文件
from LogHelper import LogHelper
from datetime import datetime
import logging
from typing import Optional, List, Dict, Union, Tuple
import time
from typing import Optional, List, Dict
from ConditionalSelection import FutuStockFilter
from tqdm import tqdm
import pandas as pd
import time
# 基本用法(自动创建日期日志+控制台输出)
logger = LogHelper(logger_name = 'KLine').setup()
def get_market_data(market: Market) -> List[str]:
"""
@@ -39,13 +41,13 @@ def get_market_data(market: Market) -> List[str]:
if ret == RET_OK:
# 提取code列并转换为列表
codes = data['code'].astype(str).tolist()
logging.info(f"获取到 {market} 市场 {len(codes)} 个股票代码")
logger.info(f"获取到 {market} 市场 {len(codes)} 个股票代码")
return codes
else:
logging.error(f"获取股票代码失败: {data}")
logger.error(f"获取股票代码失败: {data}")
return []
except Exception as e:
logging.error(f"获取股票代码时发生异常: {str(e)}")
logger.error(f"获取股票代码时发生异常: {str(e)}")
return []
finally:
quote_ctx.close()
@@ -89,7 +91,7 @@ def preprocess_quote_data(df: pd.DataFrame, float_share: Optional[int] = None) -
}
processed_data.append(item)
except Exception as e:
logging.warning(f"处理行情数据时跳过异常行 {row.get('code', '未知')}: {str(e)}")
logger.warning(f"处理行情数据时跳过异常行 {row.get('code', '未知')}: {str(e)}")
continue
return processed_data
@@ -115,7 +117,7 @@ def get_float_share(quote_ctx: OpenQuoteContext, code: str) -> Optional[int]:
return int(float_share)
return None
except Exception as e:
logging.error(f"获取股票 {code} 的流通股数量失败: {str(e)}")
logger.error(f"获取股票 {code} 的流通股数量失败: {str(e)}")
return None
def get_float_share_data(db_config: dict, table_name: str) -> Optional[Dict[str, int]]:
@@ -139,7 +141,7 @@ def get_float_share_data(db_config: dict, table_name: str) -> Optional[Dict[str,
""")
if not data:
logging.error(f"{table_name} 中没有流通股本数据")
logger.error(f"{table_name} 中没有流通股本数据")
return None
# 构建股票代码到流通股数量的映射字典
@@ -150,11 +152,11 @@ def get_float_share_data(db_config: dict, table_name: str) -> Optional[Dict[str,
if stock_code and float_share is not None:
float_share_dict[stock_code] = int(float_share)
logging.info(f"成功从 {table_name} 表加载 {len(float_share_dict)} 条流通股数据")
logger.info(f"成功从 {table_name} 表加载 {len(float_share_dict)} 条流通股数据")
return float_share_dict
except Exception as e:
logging.error(f"从数据库读取流通股本数据失败: {str(e)}")
logger.error(f"从数据库读取流通股本数据失败: {str(e)}")
return None
def save_quotes_to_db(db_config: dict, quote_data: pd.DataFrame, table_name: str = 'stock_quotes', float_share: Optional[int] = None) -> bool:
@@ -173,7 +175,7 @@ def save_quotes_to_db(db_config: dict, quote_data: pd.DataFrame, table_name: str
# 预处理数据
processed_data = preprocess_quote_data(quote_data, float_share)
if not processed_data:
logging.error("没有有效数据需要保存")
logger.error("没有有效数据需要保存")
return False
# 动态生成SQL插入语句
@@ -230,13 +232,13 @@ def save_quotes_to_db(db_config: dict, quote_data: pd.DataFrame, table_name: str
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='股票行情数据表'
"""
db.execute_update(create_table_sql)
logging.info(f"创建了新表: {table_name}")
logger.info(f"创建了新表: {table_name}")
affected_rows = db.execute_many(insert_sql, processed_data)
logging.info(f"成功插入/更新 {affected_rows} 条行情记录到表 {table_name}")
logger.info(f"成功插入/更新 {affected_rows} 条行情记录到表 {table_name}")
return True
except Exception as e:
logging.error(f"保存行情数据到表 {table_name} 失败: {str(e)}")
logger.error(f"保存行情数据到表 {table_name} 失败: {str(e)}")
return False
def read_single_account_stock_codes(file_path='data\missing_tables_小航富途.txt'):
@@ -262,22 +264,28 @@ def get_stock_codes() -> List[str]:
results = db.execute_query(sql)
return [row['stock_code'] for row in results if row['stock_code']]
except Exception as e:
logging.error(f"获取股票代码失败: {str(e)}")
logger.error(f"获取股票代码失败: {str(e)}")
return []
def write_missing_codes_to_txt(missing_codes: list, filename: str = "config\HK_futu.txt"):
"""将缺失的股票代码追加到TXT文件如果文件不存在则创建"""
try:
# 确保目录存在
directory = os.path.dirname(filename)
if directory and not os.path.exists(directory):
os.makedirs(directory)
logger.info(f"创建目录: {directory}")
# # 检查文件是否存在
# file_exists = os.path.exists(filename)
with open(filename, 'a', encoding='utf-8') as f:
for code in missing_codes:
f.write(f"\n{code}")
logger.info(f"已将 {len(missing_codes)} 个缺失表对应的股票代码写入 {filename}")
except Exception as e:
logger.error(f"写入TXT文件失败: {str(e)}")
if __name__ == "__main__":
# 基本用法(自动创建日期日志+控制台输出)
logger = LogHelper().setup()
# # 高级用法(自定义配置)
# logger = LogHelper(
# level=logging.DEBUG,
# log_dir="my_logs",
# format='%(levelname)s - %(message)s'
# ).setup()
# 数据库配置
db_config = {
'host': 'localhost',
@@ -286,19 +294,25 @@ if __name__ == "__main__":
'database': 'hk_kline_1d'
}
# 创建导入器并运行, 用于每日更新流通股数量
# 创建导入器并运行, 用于每日更新流通股数量 -> 操作界面中增加一个开关,每天只需要更新一次
futuStockFilter = FutuStockFilter(db_config)
futuStockFilter.run_direct_import()
# 每个账号获取的数据独立开来
# 每个账号获取的数据独立开来 -> 操作见面可以选择
market_data_all = get_stock_codes()
market_data_hang = read_single_account_stock_codes('config\hang_futu.txt')
market_data_kevin= read_single_account_stock_codes('config\kevin_futu.txt')
market_data = list(set(market_data_all) - set(market_data_hang) - set(market_data_kevin))
market_data_HK= read_single_account_stock_codes('config\HK_futu.txt')
market_data_new = list(set(market_data_all) - set(market_data_hang) - set(market_data_kevin) - set(market_data_HK))
# 每天收盘后更新数据
start_date = (datetime.now() - timedelta(days = 3 * 365)).strftime("%Y-%m-%d")
# write_missing_codes_to_txt(market_data_new) # 新股票添加到文件中 -> 暂时手动设置
# 动态调整
"""*********************************************"""
market_data = market_data_new
# 每天收盘后更新数据 -> 操作界面中,这个参数需要放出来
start_date = (datetime.now() - timedelta(days = 5)).strftime("%Y-%m-%d")
end_date = (datetime.now() + timedelta(days = 1)).strftime("%Y-%m-%d")
# 获取流通股数据字典
@@ -319,7 +333,7 @@ if __name__ == "__main__":
if float_share is not None:
logger.info(f"从API获取股票 {code} 的流通股数量: {float_share}")
else:
logging.warning(f"无法获取股票 {code} 的流通股数量")
logger.warning(f"无法获取股票 {code} 的流通股数量")
# 获取历史K线数据
ret, data, page_req_key = quote_ctx.request_history_kline(code, start=start_date, end=end_date, max_count=100)
@@ -341,9 +355,9 @@ if __name__ == "__main__":
else:
logger.error(f'error:{ data}')
time.sleep(1)
time.sleep(0.7)
if isWhile == False:
time.sleep(1)
time.sleep(0.7)
quote_ctx.close() # 结束后记得关闭当条连接,防止连接条数用尽

View File

@@ -1,197 +0,0 @@
"""
该代码用于更新日 K 数据
"""
from futu import *
from pymysql import Error
from MySQLHelper import MySQLHelper # MySQLHelper类保存为单独文件
from datetime import datetime
import logging
from typing import Optional, List, Dict, Union, Tuple
import time
import akshare as ak
def get_market_data(market: Market) -> List[str]:
"""
从Futu API获取指定市场的股票代码列表
Args:
market (Market): 市场枚举值,如 Market.SH, Market.SZ
Returns:
List[str]: 股票代码列表
"""
quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111)
try:
ret, data = quote_ctx.get_stock_basicinfo(market, SecurityType.STOCK)
if ret == RET_OK:
# 提取code列并转换为列表
codes = data['code'].astype(str).tolist()
logging.info(f"获取到 {market} 市场 {len(codes)} 个股票代码")
return codes
else:
logging.error(f"获取股票代码失败: {data}")
return []
except Exception as e:
logging.error(f"获取股票代码时发生异常: {str(e)}")
return []
finally:
quote_ctx.close()
def preprocess_quote_data(df: pd.DataFrame) -> List[Dict]:
"""
预处理行情数据,转换为适合数据库存储的格式
Args:
df (pd.DataFrame): 原始行情数据DataFrame
Returns:
List[Dict]: 处理后的数据列表
"""
processed_data = []
for _, row in df.iterrows():
try:
# 提取市场标识
# market = row['code'].split('.')[0] if '.' in row['code'] else 'UNKNOWN'
# 转换时间格式
# trade_time = datetime.strptime(row['date'], '%Y-%m-%d %H:%M:%S')
item = {
'trade_date': str(row['date']),
'open_price': float(row['open']),
'close_price': float(row['close']),
'high_price': float(row['high']),
'low_price': float(row['low']),
'volume': int(row['volume']) if pd.notna(row['volume']) else None,
}
processed_data.append(item)
except Exception as e:
logging.warning(f"处理行情数据时跳过异常行 {row.get('code', '未知')}: {str(e)}")
continue
return processed_data
def save_quotes_to_db(db_config: dict, quote_data: pd.DataFrame, table_name: str = 'stock_quotes') -> bool:
"""
将行情数据保存到数据库
Args:
db_config (dict): 数据库配置
quote_data (pd.DataFrame): 行情数据DataFrame
table_name (str): 目标表名(默认为'stock_quotes')
Returns:
bool: 是否成功保存
"""
# 预处理数据
processed_data = preprocess_quote_data(quote_data)
if not processed_data:
logging.error("没有有效数据需要保存")
return False
# 动态生成SQL插入语句
insert_sql = f"""
INSERT INTO {table_name} (
trade_date, open_price, close_price, high_price, low_price, volume
) VALUES (
%(trade_date)s, %(open_price)s, %(close_price)s, %(high_price)s, %(low_price)s, %(volume)s
)
ON DUPLICATE KEY UPDATE
open_price = VALUES(open_price),
close_price = VALUES(close_price),
high_price = VALUES(high_price),
low_price = VALUES(low_price),
volume = VALUES(volume)
"""
try:
with MySQLHelper(**db_config) as db:
# 检查表是否存在,不存在则创建
if not db.table_exists(table_name):
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
id INT AUTO_INCREMENT PRIMARY KEY,
trade_date DATETIME NOT NULL COMMENT '交易日期时间',
open_price DECIMAL(10, 3) COMMENT '开盘价',
close_price DECIMAL(10, 3) COMMENT '收盘价',
high_price DECIMAL(10, 3) COMMENT '最高价',
low_price DECIMAL(10, 3) COMMENT '最低价',
volume BIGINT COMMENT '成交量(股)',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY idx_trade_date (trade_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='股票行情数据表'
"""
db.execute_update(create_table_sql)
logging.info(f"创建了新表: {table_name}")
affected_rows = db.execute_many(insert_sql, processed_data)
logging.info(f"成功插入/更新 {affected_rows} 条行情记录到表 {table_name}")
return True
except Exception as e:
logging.error(f"保存行情数据到表 {table_name} 失败: {str(e)}")
return False
def read_missing_codes_basic(file_path='data\missing_tables_小航富途.txt'):
"""基础读取方法 - 按行读取所有内容"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 去除每行末尾的换行符,并过滤空行
codes = [line.strip() for line in lines if line.strip()]
return codes
except FileNotFoundError:
print(f"文件 {file_path} 不存在")
return []
except Exception as e:
print(f"读取文件失败: {str(e)}")
return []
def get_stock_codes() -> List[str]:
"""从conditionalselection表获取所有股票代码"""
try:
with MySQLHelper(**db_config) as db:
sql = f"SELECT DISTINCT stock_code FROM conditionalselection"
results = db.execute_query(sql)
return [row['stock_code'] for row in results if row['stock_code']]
except Exception as e:
logging.error(f"获取股票代码失败: {str(e)}")
return []
if __name__ == "__main__":
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('Debug.log', encoding='utf-8'), # 关键在这里
logging.StreamHandler()
]
)
# 数据库配置
db_config = {
'host': 'localhost',
'user': 'root',
'password': 'bzskmysql',
'database': 'klinedata_1d_hk_akshare'
}
# market_data = get_market_data(Market.HK) # 获取香港市场数据,后面需要改成按照筛选来获取
# # 小航富途
# market_data_hang = read_missing_codes_basic()
#
# market_data = list(set(market_data_all) - set(market_data_hang))
nCount = 0 # 记录账号获取多少只股票的数据
market_data_all = get_stock_codes()
for code in market_data_all:
stock_hk_index_daily_sina_df = ak.stock_hk_index_daily_sina(symbol=code[3:])
custom_table_name = 'hk_' + code[3:] # 自定义表名
success = save_quotes_to_db(db_config, stock_hk_index_daily_sina_df, table_name=custom_table_name)
time.sleep(5)