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'{row["ts_code"]} | ' \
+ f'{row["industry"]} | ' \
+ f'{row["name"]} | ' \
+ f'{row["turnover_ratio"]} | ' \
+ f'{row["limit_times"]} | '
+ 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'
'
+ 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'{rm_strategy_nav_pills}' \
+ f'{dbj_strategy_nav_pills}' \
+ 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'{row["ts_code"]} | ' \
+ f'{row["history"]} | ' \
+ f'{row["close"]} | '
+ pills_content += '
'
+ pills_content += f'
'
+ 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'' \
+ 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'))