From 7cce5928872b79951944f8c522a143b737695353 Mon Sep 17 00:00:00 2001 From: rm Date: Fri, 24 Nov 2023 14:42:22 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=9F=E6=96=B0=E5=85=A8=E9=83=A8=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + fp/分钟线数据入库.py | 2 +- fp/日线数据入库.py | 186 +++++++++++++++++++++++ fp/板块数据入库.py | 264 +++++++++++++++++++++++++++++++++ fp/涨跌停数据.py | 98 +++++++++++++ fp/自选股数据.py | 309 +++++++++++++++++++++++++++++++++++++++ fp/行情指数数据.py | 250 +++++++++++++++++++++++++++++++ fp/资金出入概览.py | 90 ++++++++++++ fp_main.py | 130 ++++++++++++++++ html/bootstrap - 总结.md | 80 ++++++++++ html/calendar.html | 71 +++++++++ html/head.html | 34 +++++ html/zs.html | 43 ++++++ utils/stock_web_api.py | 67 +++++++++ 14 files changed, 1626 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 fp/日线数据入库.py create mode 100644 fp/板块数据入库.py create mode 100644 fp/涨跌停数据.py create mode 100644 fp/自选股数据.py create mode 100644 fp/行情指数数据.py create mode 100644 fp/资金出入概览.py create mode 100644 fp_main.py create mode 100644 html/bootstrap - 总结.md create mode 100644 html/calendar.html create mode 100644 html/head.html create mode 100644 html/zs.html create mode 100644 utils/stock_web_api.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50fcb36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.idea/ +*.log diff --git a/fp/分钟线数据入库.py b/fp/分钟线数据入库.py index 4ea93f0..02e3c81 100644 --- a/fp/分钟线数据入库.py +++ b/fp/分钟线数据入库.py @@ -4,7 +4,7 @@ from DB.model.StockDailyFreq import get_stock_daily_freq from DB.sqlite_db_main import SqliteDbMain, config from utils.tdxUtil import TdxUtil from utils.comm import * -from 基本信息入库 import StockInfoMain +from fp.基本信息入库 import StockInfoMain class StockDailyFreqMain: diff --git a/fp/日线数据入库.py b/fp/日线数据入库.py new file mode 100644 index 0000000..bdf0e42 --- /dev/null +++ b/fp/日线数据入库.py @@ -0,0 +1,186 @@ +import sys + +from loguru import logger +from DB.model.StockDaily import get_stock_daily +from DB.sqlite_db_main import SqliteDbMain, config +from utils.tdxUtil import TdxUtil +from utils.comm import * +from fp.基本信息入库 import StockInfoMain + + +class StockDailyMain: + + def __init__(self, ts_code=None, symbol=None, restart_id=0): + # 配置日志输出到文件和控制台 + logger.add("../log/StockDailyMain.log", rotation="500 MB", level="INFO") + logger.add(sys.stderr, level="INFO") + self.code_res = StockInfoMain().get_stock_basic(ts_code=ts_code, symbol=symbol, restart_id=restart_id) + self.db_main = SqliteDbMain(config.stock_daily_db) + self.tdx_util = TdxUtil("") + + def __filter_stock(self, code, name): + if 'ST' in name: + return False + if code.startswith('30'): + return False + if code.startswith('68'): + return False + return True + + def init_data(self): + """ + 全量入库操作 + :return: + """ + for result in self.code_res: + tdx_util = TdxUtil("") + s_type = tdx_util.get_security_type(code=result.ts_code) + if s_type in tdx_util.SECURITY_TYPE and self.__filter_stock(result.ts_code, result.name): + logger.info(f"{result.id},{result.ts_code},{result.name} 开始获取 daily 和 daily_basic_ts 数据!") + self.save_by_code(result.ts_code) + + def save_by_code(self, ts_code): + df, df_basic = self.get_daily_data(ts_code=ts_code) + if df is not None and df_basic is not None: + # 创建表 + table_name = str(ts_code).split(".")[0] + "_daily" + new_table_class = get_stock_daily(table_name=table_name) + self.db_main.create_table(new_table_class) + entries = [] + for index, row in df.iterrows(): + if '停牌' != row['trade_status']: + basic_row = df_basic[df_basic['trade_date'] == row['trade_date']] + if len(basic_row) > 0: + basic_row = basic_row.drop(columns=['ts_code', 'trade_date', 'close']) + if not basic_row.empty: + row = pd.concat([row, basic_row.iloc[0]]) + entry = new_table_class(**row) + entries.append(entry) + # 保存记录 + self.db_main.insert_all_entry(entries) + + def task_data(self, trade_date=datetime.now().strftime('%Y%m%d')): + """ + 每日定时任务,补充数据操作 + :param trade_date: + :return: + """ + df, df_basic = pd.DataFrame(), pd.DataFrame() + # 日线行情数据 + for _ in range(5): + try: + df = xcsc_pro.daily(trade_date=trade_date)[::-1] + break + except Exception as e: + logger.error(f"获取 [{trade_date}] 日线行情 出现问题:", e) + time.sleep(1) + continue + else: + logger.error("daily 重试5次依然没有解决问题,请重视~!!!") + # 获取全部股票每日重要的基本面指标 + for _ in range(5): + try: + df_basic = xcsc_pro.daily_basic_ts(trade_date=trade_date)[::-1] + break + except Exception as e: + logger.error(f"获取 [{trade_date}] daily_basic_ts 出现问题:", e) + time.sleep(1) + continue + else: + logger.error("daily_basic_ts 重试5次依然没有解决问题,请重视~!!!") + df_basic = df_basic.fillna(0) + print(f" 获取 daily 和 daily_basic_ts 数据完毕!") + for index, row in df.iterrows(): + ts_code = row['ts_code'] + tdx_util = TdxUtil("") + s_type = tdx_util.get_security_type(code=ts_code) + if s_type in tdx_util.SECURITY_TYPE and '交易' == row['trade_status']: + basic_row = df_basic[df_basic['ts_code'] == row['ts_code']] + basic_row = basic_row.drop(columns=['ts_code', 'trade_date', 'close']) + row = pd.concat([row, basic_row.iloc[0]]) + table_name = str(ts_code).split(".")[0] + "_daily" + new_table_class = get_stock_daily(table_name=table_name) + entry = new_table_class(**row) + # 判断表是否存在 + if self.db_main.has_table(entry): + self.db_main.insert_entry(entry) + + # 批量创建trade_date唯一索引 + def create_index(self): + for result in self.code_res: + tdx_util = TdxUtil("") + s_type = tdx_util.get_security_type(code=result.ts_code) + if s_type in tdx_util.SECURITY_TYPE and self.__filter_stock(result.ts_code, result.name): + print(f"{result.id},{result.ts_code},{result.name} 开始创建索引!") + table_name = str(result.ts_code).split(".")[0] + "_daily" + sql = f"CREATE UNIQUE INDEX idx_unique_trade_date_{table_name} ON `{table_name}`(trade_date)" + self.db_main.execute_sql(sql) + + # 删除指定日期的数据 + def del_data_by_date(self, trade_date): + for result in self.code_res: + tdx_util = TdxUtil("") + s_type = tdx_util.get_security_type(code=result.ts_code) + if s_type in tdx_util.SECURITY_TYPE and self.__filter_stock(result.ts_code, result.name): + table_name = str(result.ts_code).split(".")[0] + "_daily" + new_table_class = get_stock_daily(table_name=table_name) + # 判断表是否存在 + if self.db_main.has_table(new_table_class): + delete_condition = new_table_class.trade_date == trade_date + self.db_main.delete_by_condition(new_table_class, delete_condition) + + def get_daily_data(self, ts_code): + i = 0 + df, df_basic = None, None + while True: + try: + if i > 6: + break + # 尝试执行请求操作 + # 如果成功,可以跳出循环 + df = xcsc_pro.daily(ts_code=ts_code)[::-1] + df_basic = xcsc_pro.daily_basic_ts(ts_code=ts_code)[::-1] + df_basic = df_basic.fillna(0) + logger.info(f"{ts_code} 获取 daily 和 daily_basic_ts 数据完毕!") + break + except Exception as e: + i += 1 + # 捕获超时异常 + logger.warning("请求超时,等待2分钟后重试...") + time.sleep(120) # 休眠2分钟 + return df, df_basic + + def update_stock_basic(self): + """ + 更新基础表,补齐股票表 + :return: + """ + # 1. 取 中国A股基本资料 + StockInfoMain().insert_stock_basic() + self.code_res = StockInfoMain().get_stock_basic() + for result in self.code_res: + s_type = self.tdx_util.get_security_type(code=result.ts_code, name=result.name) + if s_type not in self.tdx_util.SECURITY_TYPE: + continue + # 创建表 + table_name = str(result.ts_code).split(".")[0] + "_daily" + new_table_class = get_stock_daily(table_name=table_name) + # 2. 判断表是否存在,不存在则创建 + if not self.db_main.has_table(new_table_class): + logger.info(f"{result.ts_code} 表不存在,开始创建!") + # 3. 补齐数据 + self.save_by_code(result.ts_code) + + +if __name__ == '__main__': + # current_date = datetime.now() + current_date = datetime.strptime('20231024', "%Y%m%d") + if if_run(current_date): + trade_date = current_date.strftime('%Y%m%d') + main = StockDailyMain() + # main.init_data() # 初始化全市场的数据 + main.task_data(trade_date) # 指定日期数据入库 + # main.create_index() # 创建唯一索引 + # main.del_data_by_date('20230915') # 删除指定日期数据 + # main.save_by_code(ts_code='000050.SZ') # 指定更新的股票 + # main.update_stock_basic() diff --git a/fp/板块数据入库.py b/fp/板块数据入库.py new file mode 100644 index 0000000..4108f03 --- /dev/null +++ b/fp/板块数据入库.py @@ -0,0 +1,264 @@ +from pyecharts import options as opts +from pyecharts.charts import Bar +from DB.model.ConceptSector import ConceptSector +from DB.model.IndustrySector import IndustrySector +from DB.model.StockByConceptSector import StockByConceptSector +from DB.model.StockByIndustrySector import StockByIndustrySector +from DB.sqlite_db_main import SqliteDbMain, config +from utils.comm import * + +from utils.stock_web_api import execute_stock_web_api_method, stock_web_api_industry_summary, \ + stock_web_api_concept_name + + +class SectorOpt: + + def __init__(self, trade_date=datetime.now()): + self.trade_date = trade_date.strftime('%Y%m%d') + self.db_main = SqliteDbMain(config.stock_sector_db) + + def save_hy_sector(self): + """ + 行业板块 + :return: + """ + print("开始拉取行业板块数据--->>") + hy_sector_df = execute_stock_web_api_method(func=stock_web_api_industry_summary) + if hy_sector_df is None: + return None + entries = [] + for index, row in hy_sector_df.iterrows(): + entry = IndustrySector( + trade_date=self.trade_date, + sector_name=row['板块'], + pct_change=row['涨跌幅'], + total_volume=row['总成交量'], + total_turnover=row['总成交额'], + net_inflows=row['净流入'], + rising_count=row['上涨家数'], + falling_count=row['下跌家数'], + average_price=row['均价'], + leading_stock=row['领涨股'], + leading_stock_latest_price=row['领涨股-最新价'], + leading_stock_pct_change=row['领涨股-涨跌幅'] + ) + entries.append(entry) + self.db_main.insert_all_entry(entries) + print("<<---行业板块数据拉取结束") + self.db_main.get_db_size() + + def save_gn_sector(self): + """ + 概念板块 + :return: + """ + print("开始拉取概念板块数据--->>") + # 概念当日的涨幅情况 + ths_gn_df = execute_stock_web_api_method(func=stock_web_api_concept_name) + if ths_gn_df is None: + return None + entries = [] + for index, row in ths_gn_df.iterrows(): + entry = ConceptSector( + trade_date=self.trade_date, + sector_name=row['板块名称'], + sector_code=row['板块代码'], + pct_change=row['涨跌幅'], + total_market=row['总市值'], + rising_count=row['上涨家数'], + falling_count=row['下跌家数'], + new_price=row['最新价'], + leading_stock=row['领涨股票'], + leading_stock_pct_change=row['领涨股票-涨跌幅'] + ) + entries.append(entry) + self.db_main.insert_all_entry(entries) + print("<<---概念板块数据拉取结束") + self.db_main.get_db_size() + + def stock_by_gn_sector(self): + """ + 获取概念板块个股(建议每天更新一次) + :return: + """ + for _ in range(5): + try: + # 清空表数据 + self.db_main.delete_all_table(StockByConceptSector) + ths_gn_df = ak.stock_board_concept_name_em() + break + except Exception as e: + print("概念板块个股数据拉取错误:", e) + time.sleep(1) + continue + else: + print("概念板块个股数据拉取失败!!!") + return None + entries = [] + for index, row in ths_gn_df.iterrows(): + bk, bk_code = row['板块名称'], row['板块代码'] + print(f"----------- {bk} -------------") + for _ in range(5): + try: + stock_gn_df = ak.stock_board_concept_cons_em(symbol=bk) + break + except Exception as e: + print("概念板块个股-板块成份股 数据拉取错误:", e) + time.sleep(1) + continue + else: + print("概念板块个股-板块成份股 数据拉取失败!!!") + return None + print(stock_gn_df['代码']) + for s_index, s_row in stock_gn_df.iterrows(): + entry = StockByConceptSector( + update_date=datetime.now().strftime('%Y%m%d'), + sector_name=bk, + sector_code=row['板块代码'], + stock_name=s_row['名称'], + stock_code=s_row['代码'], + ) + entries.append(entry) + self.db_main.insert_all_entry(entries) + + def stock_by_hy_sector(self): + """ + 获取行业板块个股(建议每周更新一次) + :return: + """ + for _ in range(5): + try: + # 清空表数据 + self.db_main.delete_all_table(StockByIndustrySector) + hy_sector_df = ak.stock_board_industry_summary_ths() + break + except Exception as e: + print("行业板块个股数据拉取错误:", e) + time.sleep(1) + continue + else: + print("行业板块个股数据拉取失败!!!") + return None + entries = [] + for index, row in hy_sector_df.iterrows(): + bk, bk_code = row['板块'], row['序号'] + print(f"----------- {bk} -------------") + for _ in range(5): + try: + stock_bk_df = ak.stock_board_industry_cons_ths(symbol=bk) + break + except Exception as e: + print("行业板块个股数据拉取错误:", e) + time.sleep(1) + continue + else: + print("行业板块个股数据拉取失败!!!") + return None + for s_index, s_row in stock_bk_df.iterrows(): + entry = StockByIndustrySector( + update_date=datetime.now().strftime('%Y%m%d'), + sector_name=bk, + sector_code=row['序号'], + stock_name=s_row['名称'], + stock_code=s_row['代码'], + ) + entries.append(entry) + self.db_main.insert_all_entry(entries) + + def get_hy_sector_by_stock_code(self, symbol): + """ + 根据股票代码查询所属 行业板块 + :param symbol: + :return: + """ + query = self.db_main.session.query(StockByIndustrySector) \ + .filter(StockByIndustrySector.stock_code == symbol).order_by(StockByIndustrySector.id) + return self.db_main.pandas_query_by_sql(stmt=query.statement).reset_index() + + def get_gn_sector_by_stock_code(self, symbol): + """ + 根据股票代码查询所属 概念板块 + :param symbol: + :return: + """ + query = self.db_main.session.query(StockByConceptSector) \ + .filter(StockByConceptSector.stock_code == symbol).order_by(StockByConceptSector.id) + return self.db_main.pandas_query_by_sql(stmt=query.statement).reset_index() + + def industry_generate_chart(self): + """ + 行业板块 + :return: + """ + industry_num_limit_sql = f"select sector_name from stock_industry_sector where trade_date = '{self.trade_date}' ORDER BY pct_change desc limit 15" + names_res = self.db_main.execute_sql(industry_num_limit_sql) + # 提取结果中的单个列数据(假设你要提取 "收盘价" 这一列) + sector_names = [result.sector_name for result in names_res] + + data_sql = f"SELECT sec.trade_date,sec.pct_change from " \ + f"stock_industry_sector sec , " \ + f"({industry_num_limit_sql}) sec_n " \ + f"where sec.sector_name = sec_n.sector_name " \ + f"and trade_date in (select trade_date from stock_industry_sector GROUP BY trade_date order by trade_date desc LIMIT 3)" + data_result = self.db_main.execute_sql(data_sql) + df = pd.DataFrame(data_result, columns=['trade_date', 'pct_change']) + # print(df) + grouped = df.groupby('trade_date')['pct_change'].apply(list).reset_index() + + self.__generate_chart("行业板块 —— 复盘", grouped, sector_names, 'industry_generate_chart') + + def concept_generate_chart(self): + """ + 概念板块 + :return: + """ + concept_num_limit_sql = f"select sector_name from stock_concept_sector where trade_date = '{self.trade_date}' ORDER BY pct_change desc limit 15" + names_res = self.db_main.execute_sql(concept_num_limit_sql) + # 提取结果中的单个列数据(假设你要提取 "收盘价" 这一列) + sector_names = [result.sector_name for result in names_res] + + data_sql = f"SELECT sec.trade_date,sec.pct_change from " \ + f"stock_concept_sector sec , " \ + f"({concept_num_limit_sql}) sec_n " \ + f"where sec.sector_name = sec_n.sector_name " \ + f"and trade_date in (select trade_date from stock_concept_sector GROUP BY trade_date order by trade_date desc LIMIT 3)" + data_result = self.db_main.execute_sql(data_sql) + df = pd.DataFrame(data_result, columns=['trade_date', 'pct_change']) + # print(df) + grouped = df.groupby('trade_date')['pct_change'].apply(list).reset_index() + + self.__generate_chart("概念板块 —— 复盘", grouped, sector_names, 'concept_generate_chart') + + def __generate_chart(self, title, grouped, sector_names, file_name): + c = ( + Bar(init_opts=opts.InitOpts(width="100%", height="400px")) + .add_xaxis(sector_names) + .add_yaxis(grouped.loc[0, 'trade_date'], grouped.loc[0, 'pct_change']) + .add_yaxis(grouped.loc[1, 'trade_date'], grouped.loc[1, 'pct_change']) + .add_yaxis(grouped.loc[2, 'trade_date'], grouped.loc[2, 'pct_change']) + .set_global_opts( + title_opts=opts.TitleOpts(title=title, pos_left="center"), + yaxis_opts=opts.AxisOpts(offset=5), + xaxis_opts=opts.AxisOpts(offset=5, axislabel_opts=opts.LabelOpts(rotate=45, interval=0)), + legend_opts=opts.LegendOpts(pos_top="8%") + ) + .render(f"../html/{self.trade_date}/{file_name}.html") + ) + + +if __name__ == '__main__': + current_date = datetime.now() + if if_run(current_date): + sector = SectorOpt(current_date) + # 行业板块 + # sector.save_hy_sector() + # 概念板块 + # sector.save_gn_sector() + # 生成报表 + # sector.industry_generate_chart() + # sector.concept_generate_chart() + # -------》 一下建议一周更新一次 + # 行业板块个股 + sector.stock_by_hy_sector() + # 感念板块个股 + sector.stock_by_gn_sector() diff --git a/fp/涨跌停数据.py b/fp/涨跌停数据.py new file mode 100644 index 0000000..022c22b --- /dev/null +++ b/fp/涨跌停数据.py @@ -0,0 +1,98 @@ +from pyecharts.charts import Line +from pyecharts import options as opts + +from utils.comm import * + + +class LimitList: + + def __init__(self, trade_date): + self.trade_date = trade_date.strftime('%Y%m%d') + self.start_date = return_trading_day(trade_date, -7) + self.end_date = self.trade_date + self.limit_df = pd.DataFrame() + for _ in range(5): + try: + # 涨跌停列表 + self.limit_df = xcsc_pro.limit_list_d(start_date=self.start_date, end_date=self.end_date) + break + except Exception as e: + print("涨跌停列表数据获取失败,", e) + time.sleep(1) + continue + else: + print("涨跌停列表数据获取失败次数过多,请关注!!!") + + def limit_list_chart(self): + # 按照 trade_date 和 limit 进行分组统计 + grouped = self.limit_df.groupby(['trade_date', 'limit']).size().reset_index(name='count') + + # 使用 pivot_table 将 limit 类型作为列,统计数量 + pivot_table = grouped.pivot_table(index='trade_date', columns='limit', values='count', fill_value=0) + # print(pivot_table) + # D:跌停,U:涨停,Z:炸板 + # 获取不同 limit 类型的数据 + u_list = pivot_table['U'].tolist() + d_list = pivot_table['D'].tolist() + z_list = pivot_table['Z'].tolist() + trade_dates = pivot_table.index.tolist() + + c = ( + Line(init_opts=opts.InitOpts(width="100%", height="380px")) + .add_xaxis(trade_dates) + .add_yaxis("跌停数", d_list) + .add_yaxis("涨停数", u_list) + .add_yaxis("炸板数", z_list) + .set_global_opts( + tooltip_opts=opts.TooltipOpts(trigger="axis"), + title_opts=opts.TitleOpts(title="涨跌停 —— 复盘", pos_left="center"), + yaxis_opts=opts.AxisOpts(offset=5), + xaxis_opts=opts.AxisOpts(offset=5, axislabel_opts=opts.LabelOpts(rotate=45, interval=0)), + legend_opts=opts.LegendOpts(pos_top="8%") + ) + .render(f"../html/{self.trade_date}/limit_list_chart.html") + ) + + def html_page_data(self): + """ + 生成HTML文件 + :return: + """ + print(self.limit_df) + df = self.limit_df[self.limit_df['trade_date'] == self.end_date].fillna(0).sort_values( + by=["industry", "limit_times"], + ascending=False).reset_index(drop=True) + + table_content = f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' + + for index, row in df.iterrows(): + table_content += f'' \ + f'' \ + f'' \ + f'' \ + f'' + if row['limit'] == 'D': + table_content += f'' + elif row['limit'] == 'U': + table_content += f'' + elif row['limit'] == 'Z': + table_content += f'' + table_content += '' + table_content += f'
股票代码所属行业股票名称换手率连板数状态
{row["ts_code"]}{row["industry"]}{row["name"]}{row["turnover_ratio"]}{row["limit_times"]}跌停涨停炸板
' + return table_content + + +if __name__ == '__main__': + limit = LimitList(datetime.strptime('20231108', "%Y%m%d")) + # limit.limit_list_chart() + limit.html_page_data() diff --git a/fp/自选股数据.py b/fp/自选股数据.py new file mode 100644 index 0000000..855dfb6 --- /dev/null +++ b/fp/自选股数据.py @@ -0,0 +1,309 @@ +import sys + +from sqlalchemy import and_ + +from DB.model.StockDaily import get_stock_daily +from DB.sqlite_db_main import SqliteDbMain, config +from utils.tdxUtil import TdxUtil +from fp.基本信息入库 import StockInfoMain +from utils.formula import * +from loguru import logger + +from fp.板块数据入库 import SectorOpt +from utils.comm import * + + +class OptionalStock: + def __init__(self, ts_code=None, symbol=None, restart_id=0, trade_date=datetime.now()): + self.trade_date = trade_date.strftime('%Y%m%d') + # 配置日志输出到文件和控制台 + logger.add("../log/OptionalStock.log", rotation="500 MB", level="INFO") + logger.add(sys.stderr, level="INFO") + self.code_res = StockInfoMain().get_stock_basic(ts_code=ts_code, symbol=symbol, restart_id=restart_id) + self.db_main = SqliteDbMain(config.stock_daily_db) + self.tdx_util = TdxUtil("") + self.sector = SectorOpt() + + def stock_daily(self): + rm_strategy_df, dbj_strategy_df = pd.DataFrame(), pd.DataFrame() + for result in self.code_res: + s_type = self.tdx_util.get_security_type(code=result.ts_code, name=result.name) + if s_type in self.tdx_util.SECURITY_TYPE: + table_name = str(result.ts_code).split(".")[0] + "_daily" + new_table_class = get_stock_daily(table_name=table_name) + df = self.db_main.pandas_query_by_model( + model=new_table_class, + order_col=new_table_class.id.desc(), + page_number=1, page_size=60 + )[::-1] + if len(df) > 0: + logger.info( + f"----------------<<{self.trade_date}:{result.ts_code} 开始执行 N日新高策略>>----------------") + rm_strategy_df = rm_strategy_df.append(self.n_high_strategy(df=df)) + logger.info( + f"----------------<<{self.trade_date}:{result.ts_code} 开始执行 连板策略>>----------------") + dbj_strategy_df = dbj_strategy_df.append(self.conn_plate_strategy(df=df)) + return rm_strategy_df, dbj_strategy_df + + def n_high_strategy(self, df): + rm_strategy_df = pd.DataFrame() + rm_kdj = RM_KDJ(df['close'], df['high'], df['low'], 9, 3, 3) + df["KDJ_K"], df["KDJ_D"], df["KDJ_J"] = rm_kdj["KDJ_K"], rm_kdj["KDJ_D"], rm_kdj["KDJ_J"] + total, rise, fall, tomorrow_rise, tomorrow_fall = 0, 0, 0, 0, 0 + for index, row in df.iterrows(): + trade_date, code, close = row["trade_date"], row['ts_code'], row['close'] + d, j = row['KDJ_D'], row['KDJ_J'] + # 返回所要找的数据 + if trade_date == self.trade_date: + # TODO 这里有问题,d和J相同 + logger.info(f'{code} -- >> {trade_date} -->> d={d} --> j={j}') + # 超买区间,未来跌幅 + if d > 80 and j > 100: + rm_strategy_df = rm_strategy_df.append(row) + # rise, tomorrow_rise, total = self.back_testing(df, index, rise, tomorrow_rise, total, trade_date) + rm_strategy_df.reset_index(drop=True, inplace=True) + # if total > 0: + # console = f"满足策略:{total} 次,明日上涨概率:{round((tomorrow_rise / total) * 100, 2)} %," \ + # f"三日后上涨概率:{round((rise / total) * 100, 2)} %" + # # logger.info(console) + # rm_strategy_df['history'] = console + return rm_strategy_df + + def back_testing(self, df, index, rise, tomorrow_rise, total, trade_date): + if index > 3: + # 明天继续下跌的概率 + tomorrow_pre = df.iloc[-index:-index + 1]['pct_chg'].values[0] + # 未来三天最大涨幅 + max_h = df.iloc[-index:-index + 3]['pct_chg'].max().round(2) + # 计算最大回撤 + h_max = df.iloc[-index:-index + 3]['high'].max() + l_min = df.iloc[-index:-index + 3]['low'].min() + mac_ret = f"{(h_max - l_min) / l_min:.2%}" + # 平均涨幅 + mean_h = df.iloc[-index:-index + 3]['pct_chg'].mean().round(2) + total += 1 + content = f"{trade_date} --> 明日涨幅: {tomorrow_pre}% " \ + f"--> 3日最大涨幅:{max_h}%" \ + f"--> 3日最大回撤:{mac_ret}" \ + f"--> 3日平均涨幅:{mean_h}" + # if trade_date > "20230101": + # logger.info(content) + # print(content) + if tomorrow_pre > 0: + tomorrow_rise += 1 + if max_h > 0: + rise += 1 + return rise, tomorrow_rise, total + + def ma_strategy(self, df): + # MA5:当MA5在全部MA的最上面时 + MA5, MA10, MA20, MA60 = MA(df['close'], 5), MA(df['close'], 10), MA(df['close'], 20), MA( + df['close'], 60) + pass + + def conn_plate_strategy(self, df): + dbj_strategy_df = pd.DataFrame() + df['JXNH'] = JXNH(df['close'], df['open'], df['volume']) + total, rise, fall, tomorrow_rise, tomorrow_fall = 0, 0, 0, 0, 0 + for index, row in df.iterrows(): + trade_date, code, close, jxnh = row["trade_date"], row['ts_code'], row['close'], row['JXNH'] + if jxnh: + # 返回所要找的数据 + if trade_date == self.trade_date: + dbj_strategy_df = dbj_strategy_df.append(row) + rise, tomorrow_rise, total = self.back_testing(df, index, rise, tomorrow_rise, total, trade_date) + dbj_strategy_df.reset_index(drop=True, inplace=True) + if total > 0: + console = f"满足策略:{total} 次,明日上涨概率:{round((tomorrow_rise / total) * 100, 2)} %," \ + f"三日后上涨概率:{round((rise / total) * 100, 2)} %" + # print(console) + # logger.info(console) + dbj_strategy_df['history'] = console + return dbj_strategy_df + + def html_page_data(self): + rm_strategy_df, dbj_strategy_df = self.stock_daily() + # tdx_util = TdxUtil('D:\\new_tdx') + # tdx_util.set_zxg_file(cont=dbj_strategy_df['ts_code']) + # 策略1 + id_name = "v-pills-rm" + rm_strategy_nav_pills = f'' + rm_strategy_nav_pills_content = self.__get_pills_content(rm_strategy_df, id_name, active=True) + + # 策略2 + id_name = "v-pills-dbj" + dbj_strategy_nav_pills = f'' + dbj_strategy_nav_pills_content = self.__get_pills_content(dbj_strategy_df, id_name) + + res_html = f'
' \ + f'
' \ + f'{rm_strategy_nav_pills_content}' \ + f'{dbj_strategy_nav_pills_content}' \ + f'
' + return res_html + + def __get_pills_content(self, df, id_name, active=False): + pills_content = '' + if len(df) > 0: + pills_content += f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' + for index, row in df.iterrows(): + pills_content += f'' \ + f'' \ + f'' + pills_content += '' + pills_content += f'
股票代码策略表现购买建议
{row["ts_code"]}{row["history"]}{row["close"]}
' + else: + pills_content += '

暂无数据...

' + pills_div = f'
{pills_content}
' + return pills_div + + def process_data(self, sublist): + n = 16 + start_date = return_trading_day(self.trade_date, -n) + res_df = pd.DataFrame(columns=["symbol"]) + # 构建子查询字符串,例如:SELECT * FROM table1 UNION ALL SELECT * FROM table2 ... + subquery = " UNION ALL ".join( + [f"SELECT * FROM '{table_name}' where trade_date between '{start_date}' and '{self.trade_date}'" for + table_name in sublist]) + # 将子查询字符串添加到主查询中 + df = self.db_main.execute_sql_to_pandas(subquery) + # print(df) + # 使用 groupby 对数据进行分组 + grouped = df.groupby('ts_code') + # 遍历分组后的数据 + for name, group in grouped: + group = group.reset_index(drop=True) + # print(f"ts_code: {name}") + # print(group) + selected_indexes = group[group['trade_date'] == self.trade_date].index + if selected_indexes.empty or selected_indexes.values[0] != n: + continue + now_close, now_high, symbol, ts_code = group.loc[selected_indexes, 'close'].values[0], \ + group.loc[selected_indexes, 'high'].values[0], \ + group.loc[selected_indexes, 'ts_code'].values[0].split('.')[0], \ + group.loc[selected_indexes, 'ts_code'].values[0] + max_high = group.iloc[0:n]['high'].max() + cond = [ + now_close > max_high, + max_high > group.loc[selected_indexes - 1, 'high'].values[0] + ] + if not all(cond): + continue + # 计算倾斜角度 + skewness = round(sum(np.diff(group.iloc[0:n]['high'].tolist())), 3) + turnover_rate, volume_ratio = group.loc[selected_indexes, 'turnover_rate'].values[0], \ + group.loc[selected_indexes, 'volume_ratio'].values[0] + cond = [ + turnover_rate > 5, + volume_ratio > 1.5 + ] + if not all(cond): + continue + logger.info("====达成条件,开始进行进一步筛选====") + # 获取所属板块 + gn_df = self.sector.get_gn_sector_by_stock_code(symbol) + hy_df = self.sector.get_hy_sector_by_stock_code(symbol) + logger.info( + f"code={symbol},now_close={now_close},last_high={max_high}," + f"换手率={turnover_rate},量比={volume_ratio}," + f"趋势={skewness}," + f"所属行业板块:{hy_df['sector_name'].to_numpy()},所属概念板块:{gn_df['sector_name'].to_numpy()}") + + res_df = res_df.append({ + "symbol": symbol, + "ts_code": ts_code, + "趋势": skewness, + "行业板块": hy_df['sector_name'].to_numpy(), + "概念板块": gn_df['sector_name'].to_numpy() + }, ignore_index=True) + return res_df + + def more_formula(self, sublist): + # 获取删选出的结果集 + df = self.process_data(sublist=sublist) + # 获取指标需要用到的日期数据 + start_date = return_trading_day(self.trade_date, -250) + # 获取数据 + for index, row in df.iterrows(): + table_name = row["symbol"] + "_daily" + # 使用 and_ 和 between 执行范围查询 + stock_daily = get_stock_daily(table_name=table_name) + res_df = self.db_main.pandas_query_by_condition( + model=stock_daily, + query_condition=and_(stock_daily.trade_date >= start_date)) + selected_indexes = res_df[res_df['trade_date'] == self.trade_date].index + c, o, v = res_df['close'], res_df['open'], res_df['volume'] + # 获取MA + res_df['MA5'], res_df['MA10'], res_df['MA20'], res_df['MA60'], res_df['MA120'], res_df['MA250'] = \ + MA(c, 5), MA(c, 10), MA(c, 20), MA(c, 60), MA(c, 120), MA(c, 250) + # 出现 JXNH 买点 + res_df['JXNH'] = JXNH(c, o, v) + # 未来三天的整体表现 + + pass + + def junit_strategy(self): + # 测量代码块的执行时间 + start_time_total = time.time() + table_array = [] + for result in self.code_res: + s_type = self.tdx_util.get_security_type(code=result.ts_code, name=result.name) + if s_type in self.tdx_util.SECURITY_TYPE: + table_name = str(result.ts_code).split(".")[0] + "_daily" + table_array.append(table_name) + # 将 table_array 分成 num_sub_lists 个子列表 + num_sub_lists = 10 + chunk_size = len(table_array) // num_sub_lists + table_sub_lists = [table_array[i:i + chunk_size] for i in range(0, len(table_array), chunk_size)] + res_df = pd.DataFrame(columns=["symbol"]) + # 对每个子列表执行查询并合并结果 + for sublist in table_sub_lists: + res_df = res_df.append(self.process_data(sublist)) + print(res_df.reset_index(drop=True)) + # tdx_util = TdxUtil('D:\\new_tdx') + # tdx_util.set_zxg_file(cont=res_df['symbol']) + # 结束时间 + logger.info(f"总执行时间: {time.time() - start_time_total / 60:.2f} 分") + self.future(res_df) + + def future(self, df): + n = 3 + end_date = return_trading_day(self.trade_date, n) + for index, row in df.iterrows(): + stock_daily = get_stock_daily(table_name=row['symbol'] + "_daily") + future_df = self.db_main.pandas_query_by_condition( + model=stock_daily, + query_condition=stock_daily.trade_date.between(self.trade_date, end_date)) + selected_indexes = future_df[future_df['trade_date'] == self.trade_date].index + print(selected_indexes) + if selected_indexes.empty: + continue + now_chg = future_df.loc[selected_indexes, 'pct_chg'].values[0] + tomorrow_chg = future_df.loc[selected_indexes + 1, 'pct_chg'].values[0] + sum_chg = round(future_df.iloc[1:n + 1]["pct_chg"].sum(), 2) + # todo 将结果输出到CSV文件中 + # df.to_csv(f'/Users/renmeng/work_space/python_work/qnloft-get-web-everything/股票金融/量化交易/股票数据/{code}.csv', + # mode='a', header=False, index=False) + logger.info(f"未来走势-----》》》》") + logger.info( + f'【 {row["symbol"]} 】板块:【{row["行业板块"]}】,【{row["概念板块"]}】-- > \n' + f'当日涨幅:{now_chg},趋势:{row["趋势"]} \n' + f'{future_df["trade_date"][0]} 涨幅 -- > {tomorrow_chg} , 三日总涨幅:{sum_chg}' + ) + + +if __name__ == '__main__': + date_obj = datetime.strptime('20231016', "%Y%m%d") + opt_stack = OptionalStock(trade_date=date_obj) + opt_stack.junit_strategy() +# print(opt_stack.html_page_data()) diff --git a/fp/行情指数数据.py b/fp/行情指数数据.py new file mode 100644 index 0000000..e7d1e62 --- /dev/null +++ b/fp/行情指数数据.py @@ -0,0 +1,250 @@ +from datetime import timedelta + +import numpy as np +import pandas + +from utils.comm import * + + +class AllIndex: + # 中国的指数 + ZH_INDEX = pd.DataFrame( + [["上证指数", "000001.SH"], ["深证成指", "399001.SZ"], ["创业板指", "399006.SZ"], ["中证100", "000903.SH"], + ["中证100", "399903.SZ"]], + columns=['name', 'value']) + + # 世界的指数 + GLOBAL_INDEX = ['道琼斯', '标普500', '纳斯达克', '恒生指数', '日经225', '韩国KOSPI', '美元指数'] + + METAL_INDEX = pd.DataFrame( + [["黄金", "Au99.99"] + # , ["白银", "Ag99.99"] + ], columns=['name', 'value']) + + def __init__(self, trade_date): + self.trade_date = trade_date.strftime('%Y%m%d') + self.start_date = return_trading_day(trade_date, -29) + self.end_date = self.trade_date + + def calendar_page_json(self): + """ + 日历页面数据,只要A股指数 + 获取1年内的全部数控,生成一个json文件 + :return: + """ + # 计算一年前的日期 + start_date = datetime.strptime(self.end_date, '%Y%m%d') - timedelta(days=365) + start_date = start_date.strftime("%Y%m%d") + # 在首页显示 这三个的当日最新涨跌情况 + index_page_display = ["000001.SH", "399001.SZ", "399006.SZ"] + res_pandas = pandas.DataFrame(columns=['title', 'start', 'textColor', 'backgroundColor']) + for stock in index_page_display: + for _ in range(5): + try: + # 获取单个指数日线行情 + df = xcsc_pro.index_daily( + ts_code=stock, + start_date=start_date, + end_date=self.end_date, + fields="ts_code,trade_date,pct_chg") + break + except Exception as e: + print("获取单个指数日线行情 出现问题:", e) + time.sleep(1) + continue + else: + print("重试5次依然没有解决问题,请重视~!!!") + return None + # 获取对应的中文名称 + name = self.ZH_INDEX.loc[self.ZH_INDEX['value'] == stock, 'name'].iloc[0] + df['pct_chg_format'] = (round(df['pct_chg'], 2)).astype("str") + "%" + df['title'] = name + " --> " + df['pct_chg_format'] + # 将字符串转换为日期时间对象 + df['trade_date'] = pd.to_datetime(df['trade_date']) + # 格式化日期时间列为 "YYYY-MM-DD" 形式 + df['start'] = df['trade_date'].dt.strftime("%Y-%m-%d") + # 设置条件 + conditions = [ + (df['pct_chg'] > 1), + (df['pct_chg'] < -1), + ((-1 <= df['pct_chg']) & (df['pct_chg'] < 0)), + ((0 < df['pct_chg']) & (df['pct_chg'] <= 1)) + ] + # 字体颜色 当涨跌幅大于1的时候,将字体颜色设置成白色 #e60000 + text_colors = ['#ffffff', '#ffffff', '#5fcf57', '#e60000'] + df['textColor'] = np.select(conditions, text_colors) + # 背景颜色 + background_colors = ['#e60000', '#5fcf57', '#ffffff', '#ffffff'] + df['backgroundColor'] = np.select(conditions, background_colors) + + df = df[['title', 'start', 'textColor', 'backgroundColor']] + res_pandas = pd.concat([res_pandas, df], ignore_index=True) + return res_pandas + + def html_page_data(self): + # A股指数 + a_all_df, a_table_date, a_home_page_df = self.a_data() + # 全球部分指数 + g_all_df, g_table_date, g_home_page_df = self.global_index() + # 贵金属行情 + s_all_df, s_table_date, s_home_page_df = self.spot_hist_sge() + # 生成首页的徽章 + badge = "" + badge += self.__create_badge(a_home_page_df) + badge += self.__create_badge(g_home_page_df) + badge += self.__create_badge(s_home_page_df) + + # 生成 指数数据 页 + card_deck_df = pd.concat([a_table_date, g_table_date], axis=0) + card_deck_df = pd.concat([card_deck_df, s_table_date], axis=0) + card_deck = self.__create_card_deck(card_deck_df) + return badge, card_deck + + def __create_badge(self, df): + # 生成首页的徽章 + badge = "" + for index, row in df.iterrows(): + badge_color = "badge-success" + if row['pct_chg'] > 0: + badge_color = "badge-danger" + badge += f"
{row['name']}:{row['pct_chg']}
" + return badge + + def __create_card_deck(self, df): + grouped = df.groupby('name') + card_deck = '
' + row = 1 + for group_name, group_df in grouped: + card_deck_color = "border-success" + if group_df.head(1)["pct_chg"].values[0] > 0: + card_deck_color = "border-danger" + card_deck += f'
' \ + f'
{group_name}
' \ + f'
0 else "text-success"}">' \ + f'

{round(group_df.head(1)["close"].values[0], 2)}

' \ + f'

' \ + f' 0 else "text-success"} ml-3">' \ + f'{round(group_df.head(2)["close"].values[1], 2)}' \ + f'' \ + f' 0 else "text-success"} ml-auto mr-3">' \ + f'{round(group_df.head(3)["close"].values[2], 2)}' \ + f'' \ + f'

' \ + f'
' \ + f'
' + if row % 5 == 0: + card_deck += '
' + print(f"{row - 1} -- >> {len(grouped)}") + if row != len(grouped): + card_deck += '
' + if row % 5 != 0 and row == len(grouped): + card_deck += '
' + row += 1 + return card_deck + + def a_data(self): + """ + A股指数数据 + 返回 1个月内,3日,1日 + :return: + """ + # 在首页显示 这三个的当日最新涨跌情况 + index_page_display = ["000001.SH", "399001.SZ", "399006.SZ"] + all_df, home_page_df, table_df = pd.DataFrame(), pd.DataFrame(), pd.DataFrame() + for index, row in self.ZH_INDEX.iterrows(): + print(self.start_date, " --- >>", self.end_date) + # 获取单个指数日线行情 + for _ in range(5): + try: + df = xcsc_pro.index_daily(ts_code=row['value'], start_date=self.start_date, + end_date=self.end_date, + fields="ts_code,trade_date,pct_chg,close") + break + except Exception as e: + print("获取单个指数日线行情 出现问题:", e) + continue + else: + print("重试5次依然没有解决问题,请重视~!!!") + return None + df['pct_chg'] = df['pct_chg'].round(2) + df['name'] = self.ZH_INDEX.loc[self.ZH_INDEX['value'] == row['value'], 'name'].iloc[0] + # 一个月的数据 + all_df = pd.concat([all_df, df], axis=0) + if index_page_display.count(row['value']): + print(df) + # 一日的数据 + home_page_date = df.head(1) + home_page_df = pd.concat([home_page_df, home_page_date], axis=0) + # 三日的数据 + table_df_date = df.head(3) + table_df = pd.concat([table_df, table_df_date], axis=0) + return all_df, table_df, home_page_df + + def global_index(self): + """ + 国外部分指数数据 + 3日内的涨跌幅 + 1个月内的折线图 + :return: + """ + all_df, table_df, home_page_df = pd.DataFrame(), pd.DataFrame(), pd.DataFrame() + for i in self.GLOBAL_INDEX: + for _ in range(5): + try: + df = qs.get_data(i) + break + except Exception as e: + print(f"国外部分指数数据【{i}】拉取出现错误,", e) + continue + else: + print("重试5次依然没有解决问题,请重视~!!!") + return None + df['pct_chg'] = round(((df['close'] - df['open']) / df['open']) * 100, 2) + all_df_date, table_df_date, home_page_data = df.tail(30), df.tail(3), df.tail(1) + all_df = pd.concat([all_df, all_df_date], axis=0) + table_df = pd.concat([table_df, table_df_date], axis=0) + home_page_df = pd.concat([home_page_df, home_page_data], axis=0) + # pct_chg = global_df.groupby('name')['pct_chg'].apply(list).reset_index() + # print(pct_chg) + return all_df, table_df, home_page_df + + def oil_index(self): # 油价 + # 调价日期 + energy_oil_hist_df = ak.energy_oil_hist() + print(energy_oil_hist_df) + formatted_date = energy_oil_hist_df['调整日期'].iloc[-3].strftime('%Y%m%d') + # 地区油价 + energy_oil_detail_df = ak.energy_oil_detail(date=formatted_date) + energy_oil_detail_df = energy_oil_detail_df[energy_oil_detail_df['地区'] == '北京'] + print(energy_oil_detail_df) + + def spot_hist_sge(self): + all_df, table_df, home_page_df = pd.DataFrame(), pd.DataFrame(), pd.DataFrame() + for index, row in self.METAL_INDEX.iterrows(): + for _ in range(5): + try: + df = ak.spot_hist_sge(symbol=row['value']) + break + except Exception as e: + print(f"上海黄金交易所-数据资讯-行情走势-历史数据,", e) + continue + else: + print("重试5次依然没有解决问题,请重视~!!!") + return None + df['name'] = self.METAL_INDEX.loc[self.METAL_INDEX['value'] == row['value'], 'name'].iloc[0] + df['pct_chg'] = round(((df['close'] - df['open']) / df['open']) * 100, 2) + all_df_date, table_df_date, home_page_data = df.tail(30), df.tail(3), df.tail(1) + all_df = pd.concat([all_df, all_df_date], axis=0) + table_df = pd.concat([table_df, table_df_date], axis=0) + home_page_df = pd.concat([home_page_df, home_page_data], axis=0) + return all_df, table_df, home_page_df + + +if __name__ == '__main__': + # current_date = datetime.now().strftime('%Y%m%d') + current_date = datetime.strptime('20230918', "%Y%m%d") + # if if_run(current_date): + index = AllIndex(trade_date=current_date) + # index.a_index() + badge, card_deck = index.html_page_data() + print(badge) diff --git a/fp/资金出入概览.py b/fp/资金出入概览.py new file mode 100644 index 0000000..d6535e3 --- /dev/null +++ b/fp/资金出入概览.py @@ -0,0 +1,90 @@ +from pyecharts.charts import Bar, Line +import pyecharts.options as opts + +from utils.stock_web_api import execute_stock_web_api_method, stock_web_api_money_flow +from utils.comm import * + + +class MoneyFlow: + def __init__(self, trade_date): + self.trade_date = trade_date.strftime('%Y%m%d') + self.start_date = return_trading_day(trade_date, -6) + self.end_date = self.trade_date + print(self.start_date, "---", self.end_date) + + def hsgt_top10(self): + """ + 当日沪深股通十大成交股 + :return: + """ + df = xcsc_pro.hsgt_top10(trade_date=self.end_date) + print(df) + + def money_flow_chart(self): + df = execute_stock_web_api_method( + func=stock_web_api_money_flow, + start_date=self.start_date, end_date=self.end_date) + x_data = df['trade_date'].tolist() + # 沪股通 + y_axis_hgt = (round(df['hgt'] / 100, 2)).tolist() + # 深股通 + y_axis_sgt = (round(df['sgt'] / 100, 2)).tolist() + # 北上资金 + y_axis_north_money = (round(df['north_money'] / 100, 2)).tolist() + bar = ( + Bar(init_opts=opts.InitOpts(width="100%", height="400px")) + .add_xaxis(xaxis_data=x_data) + .add_yaxis( + series_name="沪股通", + y_axis=y_axis_hgt, + label_opts=opts.LabelOpts(is_show=False), + ) + .add_yaxis( + series_name="深股通", + y_axis=y_axis_sgt, + label_opts=opts.LabelOpts(is_show=False), + ) + .extend_axis( + yaxis=opts.AxisOpts( + name="金额", + type_="value", + interval=2000, + axislabel_opts=opts.LabelOpts(formatter="{value} 亿元"), + ) + ) + .set_global_opts( + tooltip_opts=opts.TooltipOpts( + is_show=True, trigger="axis", axis_pointer_type="cross" + ), + xaxis_opts=opts.AxisOpts( + type_="category", + axispointer_opts=opts.AxisPointerOpts(is_show=True, type_="shadow"), + ), + yaxis_opts=opts.AxisOpts( + name="金额", + type_="value", + interval=3000, + axislabel_opts=opts.LabelOpts(formatter="{value} 亿元"), + axistick_opts=opts.AxisTickOpts(is_show=True), + splitline_opts=opts.SplitLineOpts(is_show=True), + ), + ) + ) + line = ( + Line() + .add_xaxis(xaxis_data=x_data) + .add_yaxis( + series_name="北上资金", + # yaxis_index 0:折线图使用柱状图的y轴 1:使用extend_axis中的Y轴 + yaxis_index=0, + y_axis=y_axis_north_money, + label_opts=opts.LabelOpts(is_show=False), + ) + ) + bar.overlap(line).render(f"../复盘指标/html/{self.trade_date}/mixed_bar_and_line.html") + + +if __name__ == '__main__': + # money_flow = MoneyFlow(trade_date=datetime.now().strftime('%Y%m%d')) + money_flow = MoneyFlow(trade_date=datetime.now()) + money_flow.money_flow_chart() diff --git a/fp_main.py b/fp_main.py new file mode 100644 index 0000000..ef6da44 --- /dev/null +++ b/fp_main.py @@ -0,0 +1,130 @@ +import logging +import sys + +from jinja2 import Environment, FileSystemLoader +from loguru import logger + +from fp.新闻资讯 import News +from fp.日线数据入库 import StockDailyMain +from fp.板块数据入库 import SectorOpt +from fp.涨跌停数据 import LimitList +from fp.自选股数据 import OptionalStock +from fp.行情指数数据 import AllIndex +from fp.资金出入概览 import MoneyFlow +from utils.comm import * + + +class AppMain: + def __init__(self, trade_date=datetime.now()): + # 配置日志输出到文件和控制台 + logger.add("log/FPAppMain.log", rotation="500 MB", level="INFO") + logger.add(sys.stderr, level="INFO") + self.trade_date = trade_date.strftime('%Y%m%d') + if if_run(trade_date): + # 创建一个文件夹 + self.file_name = f'html/{self.trade_date}' + create_file(self.file_name) + # 各大指数数据 + self.index = AllIndex(trade_date=trade_date) + # 板块数据 + self.sector_opt = SectorOpt(trade_date=trade_date) + # 涨跌停 炸板 + self.limit_list = LimitList(trade_date=trade_date) + # 资金流入流出 + self.money_flow = MoneyFlow(trade_date=trade_date) + # 日线数据 + self.stock_daily = StockDailyMain() + # 新闻咨询 + self.news = News(trade_date=trade_date) + # 我的自选 + self.optional_stock = OptionalStock(trade_date=trade_date) + + def calendar_page_data(self): + logger.info(f"{self.trade_date} start--> 开始 JSON 文件生成!") + res_df = self.index.calendar_page_json() + # 将 DataFrame 转换为 JSON,并写入文件 + res_df.to_json('html/json/events.json', orient='records') + logger.info(f"{self.trade_date} --> JSON 文件已更新") + + def init_data(self): + """ + 每日数据初始化 + :return: + """ + logger.info(f"{self.trade_date} start--> 开始数据初始化!") + # 日线数据入库 + self.stock_daily.task_data(self.trade_date) + # 行业板块 入库 + self.sector_opt.save_hy_sector() + # 概念板块 入库 + self.sector_opt.save_gn_sector() + # 行业板块个股 + self.sector_opt.stock_by_hy_sector() + # 概念板块个股 + self.sector_opt.stock_by_gn_sector() + logger.info(f"{self.trade_date} end--> 数据初始化完毕!") + + def create_chart(self): + logger.info(f"{self.trade_date} start--> 开始图表页面生成!") + # 板块图表 + self.sector_opt.industry_generate_chart() + self.sector_opt.concept_generate_chart() + # 涨跌停 炸板 图表 + self.limit_list.limit_list_chart() + # 资金出入图表 + self.money_flow.money_flow_chart() + logger.info(f"{self.trade_date} end--> 图表页面生成完毕!") + + def create_page(self): + logger.info(f"{self.trade_date} start--> 开始数据静态页面生成!") + badge, card_deck = self.index.html_page_data() + limit_list_table_content = self.limit_list.html_page_data() + # 新闻数据 + cctv_content, cls_content = self.news.html_page_data() + # 首页数据 页面 + self.__write_html(template_name="index_template.html", write_html_name="index.html", + badge=badge, news_cctv=cctv_content, news_cls=cls_content) + # 指数行情 页面 + self.__write_html(template_name="zs_data_template.html", write_html_name="zs_data.html", card_deck=card_deck) + # 涨跌停 炸板 页面 + self.__write_html(template_name="limit_list_template.html", write_html_name="limit_list.html", + limit_table=limit_list_table_content) + # 我的自选 页面 (这个时间会很长) + # optional_stock_pills_tab_content = self.optional_stock.html_page_data() + # self.__write_html(template_name="zx_data_template.html", write_html_name="zx_data.html", + # pills_tabContent=optional_stock_pills_tab_content) + logger.info(f"{self.trade_date} --> 数据静态页面生成完毕!") + + def __write_html(self, template_name, write_html_name, *args, **kwargs): + print(template_name, "---", write_html_name) + print(dict(*args, **kwargs)) + # 设置 Jinja2 环境 + env = Environment(loader=FileSystemLoader('template/')) + template = env.get_template(template_name) + # 将 DataFrame 数据传递给模板并渲染 + rendered_output = template.render(dict(*args, **kwargs)) + # 将渲染后的内容写入 HTML 文件 + with open(self.file_name + f'/{write_html_name}', 'w', encoding='utf-8') as f: + f.write(rendered_output) + print("HTML rendered and saved as 'output.html'") + + def upload_ftp(self): + logger.info(f"{self.trade_date} start--> 开始FTP上传!") + # ftp上传 + # 上传json文件 + upload_files_to_ftp( + 'html/json', '/htdocs/stock/json', "events.json") + # 上传html文件 + upload_files_to_ftp( + f'html/{self.trade_date}', f'/htdocs/stock/{self.trade_date}') + logger.info(f"{self.trade_date} end--> FTP上传完毕!") + + +if __name__ == '__main__': + # date_obj = datetime.strptime('20231110', "%Y%m%d") + app = AppMain() + # app.calendar_page_data() + # app.init_data() + # app.create_chart() + # app.create_page() + # app.upload_ftp() diff --git a/html/bootstrap - 总结.md b/html/bootstrap - 总结.md new file mode 100644 index 0000000..8e72234 --- /dev/null +++ b/html/bootstrap - 总结.md @@ -0,0 +1,80 @@ +## 元素颜色 + +| 颜色代码 | 说明 | +|-----------|-----| +| primary | 蓝色 | +| secondary | 灰色 | +| success | 绿色 | +| danger | 红色 | +| warning | 黄色 | +| info | 浅蓝色 | +| light | 白色 | +| dark | 黑色 | + +## 表格添加颜色 + + +| 颜色代码 | 说明 | +|-----------------|-------| +| table-active | 选中的浅灰 | +| table-primary || +| table-secondary || +| table-success || +| table-danger || +| table-warning || +| table-info || +| table-light || +| table-dark || + + +## 元素之间添加间隔 + +| 间隔代码 | 说明 | +|--------------------------|--------| +| mb-*(margin-bottom) | 内部下边距 | +| mt-*(margin-top) | 内部上边距 | +| ml-*(margin-left) | 内部左边距 | +| mr-*(margin-right) | 内部右边距 | +| px-*(padding-horizontal) | 外部水平边距 | +| py-*(padding-vertical) | 外部垂直边距 | + +## 元素位置 + +`ml-auto` 和 `mr-auto`: 分别用于使元素在左侧和右侧具有自动外边距,从而在容器中实现水平居左或居右。 + +`mx-auto`: 用于使元素在水平方向上具有自动外边距,从而实现水平居中对齐。 + +`my-auto`: 用于使元素在垂直方向上具有自动外边距,从而实现垂直居中对齐。 + +`text-center`: 用于文本元素,使文本水平居中对齐。 + +`text-left` 和 `text-right`: 用于文本元素,分别用于使文本左对齐或右对齐。 + +justify-content-start、justify-content-center 和 justify-content-end: 用于 Flexbox 布局中的容器,分别用于在水平方向上使内容开始、居中或结束对齐。 + +align-items-start、align-items-center 和 align-items-end: 用于 Flexbox 布局中的容器,分别用于在垂直方向上使内容顶部、居中或底部对齐。 + +align-self-start、align-self-center 和 align-self-end: 用于 Flexbox 布局中的项目,分别用于在垂直方向上使单个项目的内容顶部、居中或底部对齐。 + +## 字体大小 + +`.display-1` 到 `.display-4`:这些类用于设置显示文本的不同字体大小,其中 `.display-1` 是最大的,`.display-4` 是最小的。 + +`.h1` 到 `.h6`:这些类用于设置标题的字体大小,其中 `.h1` 是最大的,`.h6`是最小的。 + +`.lead`:这个类用于设置段落或文本的字体大小,使其稍微大一些,适合用作引导文本。 + +`.small`:这个类用于设置文本的较小字体大小。 + +`.text-[size]`:这是一个自定义类,你可以用具体的字体大小名称替换 `[size]`。例如,`.text-sm` 用于设置小号字体,`.text-lg` 用于设置大号字体。 + +```html +

这是display-1文本

+

这是display-2文本

+

这是display-3文本

+

这是display-4文本

+

这是标题

+

这是引导文本

+

这是small号文本

+

这是lg号文本

+``` \ No newline at end of file diff --git a/html/calendar.html b/html/calendar.html new file mode 100644 index 0000000..5bfb90d --- /dev/null +++ b/html/calendar.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + 日历 + + + +
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/html/head.html b/html/head.html new file mode 100644 index 0000000..2b67567 --- /dev/null +++ b/html/head.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/html/zs.html b/html/zs.html new file mode 100644 index 0000000..feaf383 --- /dev/null +++ b/html/zs.html @@ -0,0 +1,43 @@ + + + + + + + + + + + 测试页面 + + +

这是display-1文本

+

这是display-2文本

+

这是display-3文本

+

这是display-4文本

+

这是标题

+

这是引导文本

+

这是small号文本

+

这是lg号文本

+ + + + + + + + + + \ No newline at end of file diff --git a/utils/stock_web_api.py b/utils/stock_web_api.py new file mode 100644 index 0000000..a9fd063 --- /dev/null +++ b/utils/stock_web_api.py @@ -0,0 +1,67 @@ +from utils.comm import * + + +def stock_web_api_method_decorator(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper + + +@stock_web_api_method_decorator +def stock_web_api_money_flow(start_date=None, end_date=None): + # 沪深港通资金流向 + df = xcsc_pro.moneyflow_hsgt(start_date=start_date, end_date=end_date)[::-1] + if df[df["trade_date"] == end_date].empty: + # df 如果里面不包含当天日期的数据,则使用备用接口 + ak_df = ak.stock_hsgt_fund_flow_summary_em() + northward = ak_df[ak_df['资金方向'] == '北向'] + hgt = round(northward[northward['板块'] == '沪股通']['成交净买额'].sum(), 2) * 100 + sgt = round(northward[northward['板块'] == '深股通']['成交净买额'].sum(), 2) * 100 + north_money = round(hgt + sgt, 2) + substitute_df = pd.DataFrame( + [{ + "trade_date": northward["交易日"].iloc[0].strftime('%Y%m%d'), + "hgt": hgt, + "sgt": sgt, + "north_money": north_money, + }], + index=[0], + ) + result = pd.concat([df, substitute_df], ignore_index=True) + return result + if df is not None and not df.empty: + return df + raise Exception("沪深港通资金流向数据拉取失败!") + + +@stock_web_api_method_decorator +def stock_web_api_industry_summary(): # 同花顺 - 行业板块数据 + df = ak.stock_board_industry_summary_ths() + if df is not None and not df.empty: + return df + raise Exception("同花顺 - 行业板块数据 数据拉取失败!") + + +@stock_web_api_method_decorator +def stock_web_api_concept_name(): # 同花顺 - 概念板块数据 + df = ak.stock_board_concept_name_em() + if df is not None and not df.empty: + return df + raise Exception("同花顺 - 概念板块数据 数据拉取失败!") + + +def execute_stock_web_api_method(func, *args, **kwargs): + for _ in range(5): + try: + return func(*args, **kwargs) + except Exception as e: + print(f"【{func}】 -- 拉取出现问题 【{e}】, 开始进行重试!") + time.sleep(1) + else: + print(f"【{func}】 5次出现错误,请关注!!!") + return None + + +if __name__ == '__main__': + print(execute_stock_web_api_method(func=stock_web_api_money_flow, start_date='20231123', end_date='20231123'))