From 926c7a1c79491ef43df7f4d13e1df0ec37e0969b Mon Sep 17 00:00:00 2001 From: jackluson <18219112108@163.com> Date: Wed, 11 Aug 2021 17:20:31 +0800 Subject: [PATCH] feat: :tada: third quarter data crawler --- output/fund_morning_quarter_catch.csv | 1 + output/xlsx/high-score-funds_log.xlsx | Bin 23464 -> 27833 bytes requirements.txt | 66 +------- src/fund_info/statistic.py | 77 ++++++---- src/fund_statistic.py | 212 +++++++++++++++----------- src/fund_strategy.py | 59 +++---- src/sql_model/fund_query.py | 8 +- src/utils/index.py | 37 +++++ 8 files changed, 255 insertions(+), 205 deletions(-) create mode 100644 output/fund_morning_quarter_catch.csv diff --git a/output/fund_morning_quarter_catch.csv b/output/fund_morning_quarter_catch.csv new file mode 100644 index 0000000..73331b6 --- /dev/null +++ b/output/fund_morning_quarter_catch.csv @@ -0,0 +1 @@ +代码,晨星专属号,名称,类型,股票总仓位,页码,备注 diff --git a/output/xlsx/high-score-funds_log.xlsx b/output/xlsx/high-score-funds_log.xlsx index f7ca5eba088df1e393382b2bd3ec4bc7a60f02ce..176c8a672689addaae7cccce0562651eefbaae14 100644 GIT binary patch literal 27833 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 %}$Dq*ZZ#rWrF^f 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 VY6L&GJQWTV&JC filter_count and key != None: filer_dict[key] = value @@ -105,14 +107,14 @@ class FundStatistic: ) code_dict = dict() for result in results: - #print(result) + # print(result) fund_info = { '基金代码': result[0], '基金名称': result[1], '基金金额': result[2], '股票总仓位': result[3], } - totol_asset = result[2] + totol_asset = result[2] for index in range(4, len(result), 3): code = result[index] name = result[index + 1] @@ -123,7 +125,8 @@ class FundStatistic: if key == None and code and name: key = str(code) + '-' + str(name) #key = str(name) - holder_asset = round(portion * totol_asset / 100, 4) if totol_asset and portion else 0 + holder_asset = round( + portion * totol_asset / 100, 4) if totol_asset and portion else 0 if(key in code_dict and code != None): code_dict[key]['count'] = code_dict[key]['count'] + 1 code_dict[key]['fund_list'].append({ @@ -136,23 +139,28 @@ class FundStatistic: code_dict[key] = { 'count': 1, 'fund_list': [{ - **fund_info, - '仓位占比': portion, - '持有市值(亿元)': holder_asset, - '仓位排名': int(index / 3) - }] + **fund_info, + '仓位占比': portion, + '持有市值(亿元)': holder_asset, + '仓位排名': int(index / 3) + }] } - #for key, value in code_dict.items(): + # for key, value in code_dict.items(): # print('key, value', key, value) - print('code_dict.items()', code_dict.items()) + # print('code_dict.items()', code_dict.items()) return list(code_dict.items()) - #return sorted(code_dict.items(), key=lambda x: x[1]['count'], reverse=True) + # return sorted(code_dict.items(), key=lambda x: x[1]['count'], reverse=True) # 分组查询特定股票的每个季度基金持有总数 + def item_stock_fund_count(self, stock_code, fund_code_pool=None): return self.each_query.select_special_stock_fund_count(stock_code, fund_code_pool) - def select_special_stock_special_quarter_info(self, stock_code, quarter_index=None,fund_code_pool=None): - result = self.each_query.select_special_stock_special_quarter_info(stock_code, quarter_index, fund_code_pool) + def select_special_stock_special_quarter_info(self, stock_code, quarter_index=None, fund_code_pool=None): + """ + 即将废弃 + """ + result = self.each_query.select_special_stock_special_quarter_info( + stock_code, quarter_index, fund_code_pool) target_stock_dict = { 'count': len(result) } @@ -163,13 +171,13 @@ class FundStatistic: code = holders[index] if code == stock_code: portion = holders[index+1] - holder_asset = round(portion * total_asset / 100, 4) if total_asset and portion else 0 + holder_asset = round( + portion * total_asset / 100, 4) if total_asset and portion else 0 total_holder_asset = total_holder_asset + holder_asset break target_stock_dict['holder_asset'] = total_holder_asset return target_stock_dict - def select_fund_pool(self, *, morning_star_rating_5="", morning_star_rating_3="", **args): return self.each_query.select_certain_condition_funds( morning_star_rating_5=morning_star_rating_5, @@ -186,7 +194,7 @@ class FundStatistic: def summary_special_funds_stock_detail(self, fund_code_pool, quarter_index=None): holder_stock_industry_list = [] for fund_code in fund_code_pool: - fund_info = self.select_special_fund_info(fund_code, quarter_index ) + fund_info = self.select_special_fund_info(fund_code, quarter_index) fund_code = fund_info[0] fund_name = fund_info[1] fund_cat = fund_info[2] @@ -199,17 +207,22 @@ class FundStatistic: stock_name = fund_info[index+1] stock_portion = fund_info[index+2] stock_index = int((index - 4) / 3) - stock_list_industry = [fund_code, fund_name,fund_cat,fund_manager, fund_total_asset, fund_total_portion, fund_ten_portion, - stock_code, stock_name, stock_portion, stock_index] - #holder_stock_industry_list.append(stock_list_industry] + stock_list_industry = [fund_code, fund_name, fund_cat, fund_manager, fund_total_asset, fund_total_portion, fund_ten_portion, + stock_code, stock_name, stock_portion, stock_index] + # holder_stock_industry_list.append(stock_list_industry] if bool(re.search("^\d{6}$", stock_code)): - stock_list_industry_list = self.select_stock_pool_industry([stock_code]) + stock_list_industry_list = self.select_stock_pool_industry([ + stock_code]) stock_list_industry_dict = stock_list_industry_list[0] - industry_name_first = stock_list_industry_dict.get('industry_name_first') - industry_name_second = stock_list_industry_dict.get('industry_name_second') - industry_name_third = stock_list_industry_dict.get('industry_name_third') - - holder_stock_industry_list.append([*stock_list_industry, industry_name_third,industry_name_second, industry_name_first]) + industry_name_first = stock_list_industry_dict.get( + 'industry_name_first') + industry_name_second = stock_list_industry_dict.get( + 'industry_name_second') + industry_name_third = stock_list_industry_dict.get( + 'industry_name_third') + + holder_stock_industry_list.append( + [*stock_list_industry, industry_name_third, industry_name_second, industry_name_first]) return holder_stock_industry_list def query_all_stock_industry_info(self): diff --git a/src/fund_statistic.py b/src/fund_statistic.py index 2c7cf8e..3231440 100644 --- a/src/fund_statistic.py +++ b/src/fund_statistic.py @@ -11,12 +11,14 @@ Copyright (c) 2020 Camel Lu ''' import time import re +import decimal +import os from pprint import pprint import pandas as pd +import numpy as np from fund_info.statistic import FundStatistic -from utils.index import get_last_quarter_str, find_from_list_of_dict -from openpyxl import load_workbook -import os +from utils.index import get_last_quarter_str, get_stock_market, find_from_list_of_dict, update_xlsx_file + def get_fund_code_pool(): # fund_code_pool = ['000001', '160133', '360014', '420002', @@ -44,10 +46,24 @@ def get_fund_code_pool(): ) return fund_code_pool -def stocks_compare(stock_list, *, quarter_index=None, fund_code_pool=None, is_A_stock=None): - each_statistic = FundStatistic() + +def stocks_compare(stock_list, *, market=None, quarter_index=None, fund_code_pool=None, is_A_stock=None): + """与某个季度数据进行比较 + """ if quarter_index == None: quarter_index = get_last_quarter_str(2) + print("quarter_index", quarter_index) + + last_quarter_input_file = './outcome/数据整理/strategy/all_stock_rank/' + \ + quarter_index + '.xlsx' + data_last_quarter = pd.read_excel(io=last_quarter_input_file, engine="openpyxl", dtype={ + "代码": np.str}, sheet_name=None) + + if market: + df_data_target_market = data_last_quarter.get(market) + df_data_target_market[quarter_index + '持有数量(只)'] = df_data_target_market[quarter_index + '持有数量(只)'].astype( + int) + each_statistic = FundStatistic() filter_list = [] for stock in stock_list: @@ -58,22 +74,25 @@ def stocks_compare(stock_list, *, quarter_index=None, fund_code_pool=None, is_A_ stock_holder_detail = stock[1] holder_count = stock_holder_detail.get('count') holder_asset = stock_holder_detail.get('holder_asset') - - last_quarter_holder_detail_dict = each_statistic.select_special_stock_special_quarter_info( - stock_code, - quarter_index, - fund_code_pool - ) - - last_holder_count = last_quarter_holder_detail_dict['count'] - last_holder_asset = last_quarter_holder_detail_dict['holder_asset'] - + if not market: + target_market = get_stock_market(stock_code) + print("target_market", target_market) + df_data_target_market = data_last_quarter.get(target_market) + target_loc = df_data_target_market[df_data_target_market['代码'] == stock_code] + last_holder_count = 0 + last_holder_asset = 0 + if len(target_loc) == 1: + col_target = quarter_index + '持有数量(只)' + last_holder_count = target_loc[col_target].iloc[0] + col_target = quarter_index + '持有市值(亿元)' + last_holder_asset = round(decimal.Decimal( + target_loc[col_target].iloc[0]), 4) diff_holder_count = holder_count - last_holder_count diff_holder_asset = holder_asset - last_holder_asset diff_holder_count_percent = '{:.2%}'.format( diff_holder_count / last_holder_count) if last_holder_count != 0 else "+∞" - + diff_holder_asset_percent = '{:.2%}'.format( diff_holder_asset / last_holder_asset) if last_holder_asset != 0 else "+∞" # flag = '📈' if diff_holder_count > 0 else '📉' @@ -85,14 +104,18 @@ def stocks_compare(stock_list, *, quarter_index=None, fund_code_pool=None, is_A_ flag_asset = 'up' if diff_holder_asset > 0 else 'down' if diff_holder_asset == 0: flag = '=' - + item_tuple = [stock_code, stock_name, holder_count, last_holder_count, diff_holder_count, diff_holder_count_percent, flag_count, holder_asset, last_holder_asset, diff_holder_asset, diff_holder_asset_percent, flag_asset] if is_A_stock: - industry_name_third = stock_holder_detail.get('industry_name_third') - industry_name_second = stock_holder_detail.get('industry_name_second') - industry_name_first = stock_holder_detail.get('industry_name_first') - item_tuple = [*item_tuple, industry_name_third, industry_name_second,industry_name_first ] + industry_name_third = stock_holder_detail.get( + 'industry_name_third') + industry_name_second = stock_holder_detail.get( + 'industry_name_second') + industry_name_first = stock_holder_detail.get( + 'industry_name_first') + item_tuple = [*item_tuple, industry_name_third, + industry_name_second, industry_name_first] # if diff_percent == "+∞" or not float(diff_percent.rstrip('%')) < -20: filter_list.append(item_tuple) @@ -100,34 +123,38 @@ def stocks_compare(stock_list, *, quarter_index=None, fund_code_pool=None, is_A_ return filter_list # T100权重股排名 + + def t100_stocks_rank(quarter_index=None, *, each_statistic): if quarter_index == None: - quarter_index = get_last_quarter_str() + quarter_index = get_last_quarter_str(1) last_quarter_index = get_last_quarter_str(2) output_file = './outcome/数据整理/strategy/top100_rank.xlsx' sheet_name = quarter_index + '基金重仓股T100' - columns=['代码', - '名称', quarter_index + '持有数量(只)', last_quarter_index +'持有数量(只)', '持有数量环比', '持有数量环比百分比', '持有数量升或降', quarter_index + '持有市值(亿元)', last_quarter_index + '持有市值(亿元)', '持有市值环比', '持有市值环比百分比', '持有市值升或降'] + columns = ['代码', + '名称', quarter_index + '持有数量(只)', last_quarter_index + '持有数量(只)', '持有数量环比', '持有数量环比百分比', '持有数量升或降', quarter_index + '持有市值(亿元)', last_quarter_index + '持有市值(亿元)', '持有市值环比', '持有市值环比百分比', '持有市值升或降'] stock_top_list = each_statistic.all_stock_fund_count( quarter_index=quarter_index, filter_count=80) stock_top_list = stock_top_list[:100] # 获取top100权重股 - #pprint(stock_top_list) + # pprint(stock_top_list) filter_list = stocks_compare(stock_top_list) df_filter_list = pd.DataFrame(filter_list, columns=columns) - df_filter_list.to_excel(output_file, sheet_name=sheet_name) + update_xlsx_file(output_file, df_filter_list, sheet_name) + # df_filter_list.to_excel(output_file, sheet_name=sheet_name) + -# 所有股票排名 def all_stocks_rank(each_statistic): - quarter_index = get_last_quarter_str(2) + """所有股票排名 + """ + quarter_index = get_last_quarter_str(1) print("quarter_index", quarter_index) - last_quarter_index = get_last_quarter_str(3) + last_quarter_index = get_last_quarter_str(2) sheet_name = last_quarter_index + '基金重仓股T100' - columns=['代码', - '名称', quarter_index + '持有数量(只)', last_quarter_index +'持有数量(只)', '持有数量环比', '持有数量环比百分比', '持有数量升或降', quarter_index + '持有市值(亿元)', last_quarter_index + '持有市值(亿元)', '持有市值环比', '持有市值环比百分比', '持有市值升或降'] - output_file = './outcome/数据整理/strategy/all_stock_rank/'+ quarter_index +'.xlsx' - + columns = ['代码', + '名称', quarter_index + '持有数量(只)', last_quarter_index + '持有数量(只)', '持有数量环比', '持有数量环比百分比', '持有数量升或降', quarter_index + '持有市值(亿元)', last_quarter_index + '持有市值(亿元)', '持有市值环比', '持有市值环比百分比', '持有市值升或降'] + output_file = './outcome/数据整理/strategy/all_stock_rank/' + quarter_index + '.xlsx' stock_top_list = each_statistic.all_stock_fund_count( quarter_index=quarter_index, @@ -145,38 +172,60 @@ def all_stocks_rank(each_statistic): #path = '港股' hk_stock_list.append(stock_name_code) elif bool(re.search("^\d{6}$", stock_code)): - #'A股/深证主板'、'A股/创业板'、'A股/上证主板'、'A股/科创板' - a_condition = bool(re.search("^(00(0|1|2|3)\d{3})|(30(0|1)\d{3})|(60(0|1|2|3|5)\d{3})|68(8|9)\d{3}$", stock_code)) - target_item = find_from_list_of_dict(all_a_stocks_industry_info_list, 'stock_code', stock_code) + # 'A股/深证主板'、'A股/创业板'、'A股/上证主板'、'A股/科创板' + a_condition = bool(re.search( + "^(00(0|1|2|3)\d{3})|(30(0|1)\d{3})|(60(0|1|2|3|5)\d{3})|68(8|9)\d{3}$", stock_code)) + target_item = find_from_list_of_dict( + all_a_stocks_industry_info_list, 'stock_code', stock_code) if a_condition and target_item: - print('stock_code',stock_code) - stock_name_code[1]['industry_name_first'] = target_item.get('industry_name_first') - stock_name_code[1]['industry_name_second'] = target_item.get('industry_name_second') - stock_name_code[1]['industry_name_third'] = target_item.get('industry_name_third') + stock_name_code[1]['industry_name_first'] = target_item.get( + 'industry_name_first') + stock_name_code[1]['industry_name_second'] = target_item.get( + 'industry_name_second') + stock_name_code[1]['industry_name_third'] = target_item.get( + 'industry_name_third') a_stock_list.append(stock_name_code) else: other_stock_list.append(stock_name_code) else: other_stock_list.append(stock_name_code) - a_stock_compare_list = stocks_compare(a_stock_list, quarter_index=last_quarter_index, is_A_stock=True) - hk_stock_compare_list = stocks_compare(hk_stock_list,quarter_index=last_quarter_index,) - other_stock_compare_list = stocks_compare(other_stock_list,quarter_index=last_quarter_index,) + a_market = 'A股' + hk_market = '港股' + other_market = '其他' + + a_stock_compare_list = stocks_compare( + a_stock_list, market=a_market, quarter_index=last_quarter_index, is_A_stock=True) + hk_stock_compare_list = stocks_compare( + hk_stock_list, market=hk_market, quarter_index=last_quarter_index,) + other_stock_compare_list = stocks_compare( + other_stock_list, market=other_market, quarter_index=last_quarter_index,) a_columns = [*columns, '三级行业', '二级行业', '一级行业'] + df_a_list = pd.DataFrame(a_stock_compare_list, columns=a_columns) df_hk_list = pd.DataFrame(hk_stock_compare_list, columns=columns) df_other_list = pd.DataFrame(other_stock_compare_list, columns=columns) writer = pd.ExcelWriter(output_file, engine='xlsxwriter') - df_a_list.to_excel(writer, sheet_name='A股') + df_a_list.to_excel(writer, sheet_name=a_market) - df_hk_list.to_excel(writer, sheet_name='港股') + df_hk_list.to_excel(writer, sheet_name=hk_market) - df_other_list.to_excel(writer, sheet_name='其他') + df_other_list.to_excel(writer, sheet_name=other_market) writer.save() -def all_stock_holder_detail(quarter_index, each_statistic, threshold=0): + +def all_stock_holder_detail(each_statistic, *, quarter_index=None, threshold=0): + """ 所有股票的基金持仓细节 + + Args: + each_statistic (class): 统计类 + quarter_index (str, optional): 季度字符串. Defaults to None. + threshold (int, optional): 输出门槛. Defaults to 0. + """ + if quarter_index == None: + quarter_index = get_last_quarter_str() stock_list = each_statistic.all_stock_fund_count_and_details( quarter_index=quarter_index, filter_count=threshold) @@ -199,84 +248,75 @@ def all_stock_holder_detail(quarter_index, each_statistic, threshold=0): else: print('stock_name_code', stock_name_code) hold_fund_count = stock[1]['count'] - hold_fund_list = sorted(stock[1]['fund_list'], key=lambda x: x['持有市值(亿元)'], reverse=True) + hold_fund_list = sorted( + stock[1]['fund_list'], key=lambda x: x['持有市值(亿元)'], reverse=True) df_list = pd.DataFrame(hold_fund_list) - #if stock_code == 'NTES': + # if stock_code == 'NTES': # print('stock_code', df_list) stock_name_code = stock_name_code.replace('-*', '-').replace('/', '-') path = './outcome/数据整理/stocks/' + path + '/' + stock_name_code + '.xlsx' path = path.replace('\/', '-') print("path", path) - #print('df_list--',stock_name_code, df_list) - if os.path.exists(path): - writer = pd.ExcelWriter(path, engine='openpyxl') - book = load_workbook(path) - # 表名重复,删掉,重写 - if quarter_index in book.sheetnames: - del book[quarter_index] - if len(book.sheetnames) == 0: - df_list.to_excel( - path, sheet_name=quarter_index) - continue - else: - writer.book = book - df_list.to_excel( - writer, sheet_name=quarter_index) - writer.save() - writer.close() - else: - df_list.to_excel( - path, sheet_name=quarter_index) -# 获取某些基金的十大持仓股票信息 + + update_xlsx_file(path, df_list, quarter_index) + + def get_special_fund_code_holder_stock_detail(quarter_index, each_statistic): - #基金组合信息 - fund_portfolio ={ + """ 获取某些基金的十大持仓股票信息 + """ + # 基金组合信息 + fund_portfolio = { '001811': { 'name': '中欧明睿新常态混合A', - 'position' : 0.2 + 'position': 0.2 }, '001705': { 'name': '泓德战略转型股票', - 'position' : 0.2 + 'position': 0.2 }, '163415': { 'name': '兴全商业模式优选混合', - 'position' : 0.2 + 'position': 0.2 }, '001043': { 'name': '工银美丽城镇主题股票A', - 'position' : 0.2 + 'position': 0.2 }, '000547': { 'name': '建信健康民生混合', - 'position' : 0.2 + 'position': 0.2 }, - '450001': { + '450001': { 'name': '国富中国收益混合', - 'position' : 0.2 + 'position': 0.2 }, } fund_code_pool = list(fund_portfolio.keys()) - holder_stock_industry_list = each_statistic.summary_special_funds_stock_detail(fund_code_pool, quarter_index) + holder_stock_industry_list = each_statistic.summary_special_funds_stock_detail( + fund_code_pool, quarter_index) path = './outcome/数据整理/funds/' + '/' + '高分权益基金组合十大持仓明细' + '.xlsx' - columns=['基金代码','基金名称', '基金类型', '基金经理', '基金总资产(亿元)', '基金股票总仓位', '十大股票仓位', '股票代码', '股票名称', '所占仓位', '所处仓位排名', '三级行业', '二级行业', '一级行业'] + columns = ['基金代码', '基金名称', '基金类型', '基金经理', '基金总资产(亿元)', '基金股票总仓位', + '十大股票仓位', '股票代码', '股票名称', '所占仓位', '所处仓位排名', '三级行业', '二级行业', '一级行业'] df_a_list = pd.DataFrame(holder_stock_industry_list, columns=columns) writer = pd.ExcelWriter(path, engine='xlsxwriter') df_a_list.to_excel(writer, sheet_name='十大持仓明细--' + quarter_index) writer.save() + + if __name__ == '__main__': each_statistic = FundStatistic() - quarter_index = "2021-Q1" + # quarter_index = "2021-Q2" + # read_excel(path, 'A股', '601888', '2021-Q1持有市值(亿元)') # 所有股票的基金持仓细节 - #all_stock_holder_detail(quarter_index, each_statistic) + # all_stock_holder_detail(each_statistic) # 获取所有股票排名,按股票市场分类输出 - all_stocks_rank(each_statistic) + # all_stocks_rank(each_statistic) # 获取Top100股票排名 - #t100_stocks_rank(each_statistic=each_statistic) - + t100_stocks_rank(each_statistic=each_statistic) + # 获取某些基金的十大持仓股票信息 #get_special_fund_code_holder_stock_detail(quarter_index, each_statistic) diff --git a/src/fund_strategy.py b/src/fund_strategy.py index 24ff227..61fbc0a 100644 --- a/src/fund_strategy.py +++ b/src/fund_strategy.py @@ -7,17 +7,23 @@ Author: luxuemin2108@gmail.com ----- Copyright (c) 2021 Camel Lu ''' + +import os from sql_model.fund_query import FundQuery import pandas as pd -from openpyxl import load_workbook,Workbook +from openpyxl import load_workbook, Workbook from openpyxl.utils import get_column_letter from utils.index import get_last_quarter_str from pprint import pprint -# 输出高分基金 -def output_high_score_funds(each_query,quarter_index=None): + +def output_high_score_funds(each_query, quarter_index=None): + """ + 输出高分基金 + """ if quarter_index == None: - quarter_index = get_last_quarter_str() + quarter_index = get_last_quarter_str() + print("quarter_index", quarter_index) high_score_funds = each_query.select_high_score_funds( quarter_index=quarter_index) columns_bk = ['代码', '名称', '季度', '总资产', '现任基金经理管理起始时间', '投资风格', '三月最大回撤', '六月最大回撤', '夏普比率', '阿尔法系数', '贝塔系数', @@ -34,27 +40,28 @@ def output_high_score_funds(each_query,quarter_index=None): # df_high_score_funds.to_excel(writer, sheet_name=quarter_index) # df2.to_excel(writer, sheet_name='Sheet2') path = './output/xlsx/high-score-funds_log.xlsx' - writer = pd.ExcelWriter(path, engine='openpyxl') - book = load_workbook(path) - writer.book = book - df_high_score_funds.to_excel(writer, sheet_name=quarter_index) - writer.save() - writer.close() + if os.path.exists(path): + writer = pd.ExcelWriter(path, engine='openpyxl') + book = load_workbook(path) + writer.book = book + # 表名重复,删掉,重写 + if quarter_index in book.sheetnames: + del book[quarter_index] -if __name__ == '__main__': - #each_query = FundQuery() - #quarter_index = '2020-Q4' - #output_high_score_funds() - dest_filename = 'empty_book.xlsx' - #wb = Workbook(dest_filename) - wb = load_workbook(filename = 'empty_book.xlsx') - ws = wb.active - print("ws", ws) - #ws.merge_cells('A2:D2') - ws.merge_cells(start_row=2, start_column=1, end_row=4, end_column=4) - ws.merge_cells('J17:J20') - ws.column_dimensions.group('A','D', hidden=True) - ws.row_dimensions.group(1,10, hidden=True) - wb.save(dest_filename) - #ws.unmerge_cells('A2:D2') + if len(book.sheetnames) == 0: + df_high_score_funds.to_excel( + path, sheet_name=quarter_index) + else: + writer.book = book + df_high_score_funds.to_excel( + writer, sheet_name=quarter_index) + writer.save() + writer.close() + else: + df_high_score_funds.to_excel( + path, sheet_name=quarter_index) + +if __name__ == '__main__': + each_query = FundQuery() + output_high_score_funds(each_query) diff --git a/src/sql_model/fund_query.py b/src/sql_model/fund_query.py index 05be90e..e6d1797 100644 --- a/src/sql_model/fund_query.py +++ b/src/sql_model/fund_query.py @@ -111,7 +111,6 @@ class FundQuery(BaseQuery): sql, [self.quarter_date, self.quarter_index, page_start, page_limit]) # 执行sql语句 return self.cursor.fetchall() # 获取查询的所有记录 - @lock_process def select_high_score_funds(self, *, quarter_index=None): """获取高分基金池 @@ -262,8 +261,8 @@ class FundQuery(BaseQuery): return () list_str = ', '.join(fund_code_pool) fund_code_list_sql = "AND t.fund_code IN (" + list_str + ")" - sql_query_quarter = "SELECT t.fund_code, t.fund_name, u.total_asset, t.stock_position_total, " + stock_sql_join + \ - " FROM fund_morning_stock_info as t LEFT JOIN fund_morning_quarter as u ON u.fund_code = t.fund_code AND u.quarter_index = t.quarter_index WHERE u.quarter_index = %s AND t.stock_position_total > 20 " + \ + sql_query_quarter = "SELECT t.fund_code, a.fund_name, u.total_asset, t.stock_position_total, " + stock_sql_join + \ + " FROM fund_morning_stock_info as t LEFT JOIN fund_morning_quarter as u ON u.fund_code = t.fund_code AND u.quarter_index = t.quarter_index LEFT JOIN fund_morning_base as a ON a.fund_code = t.fund_code WHERE u.quarter_index = %s AND t.stock_position_total > 20 " + \ fund_code_list_sql + \ ";" # 大于20%股票持仓基金 if quarter_index == None: @@ -299,6 +298,9 @@ class FundQuery(BaseQuery): # def select_special_stock_special_quarter_info(self, stock_code, quarter_index=None, fund_code_pool=None): + """ + 即将废弃 + """ if quarter_index == None: quarter_index = self.quarter_index diff --git a/src/utils/index.py b/src/utils/index.py index 05185c6..6521efc 100644 --- a/src/utils/index.py +++ b/src/utils/index.py @@ -2,8 +2,13 @@ import time import datetime import os + +import re from threading import Thread, Lock +import pandas as pd +from openpyxl import load_workbook + def lock_process(func): lock = Lock() @@ -125,6 +130,38 @@ def find_from_list_of_dict(dict_list, match_key, value): return res +def get_stock_market(stock_code): + if bool(re.search("^\d{5}$", stock_code)): + return '港股' + elif bool(re.search("^\d{6}$", stock_code)) and bool(re.search( + "^(00(0|1|2|3)\d{3})|(30(0|1)\d{3})|(60(0|1|2|3|5)\d{3})|68(8|9)\d{3}$", stock_code)): + return 'A股' + else: + return '其他' + + +def update_xlsx_file(path, df_data, sheet_name): + if os.path.exists(path): + writer = pd.ExcelWriter(path, engine='openpyxl') + book = load_workbook(path) + # 表名重复,删掉,重写 + if sheet_name in book.sheetnames: + del book[sheet_name] + if len(book.sheetnames) == 0: + df_data.to_excel( + path, sheet_name=sheet_name) + return + else: + writer.book = book + df_data.to_excel( + writer, sheet_name=sheet_name) + writer.save() + writer.close() + else: + df_data.to_excel( + path, sheet_name=sheet_name) + + def bootstrap_thread(target_fn, total, thread_count=2): threaders = [] start_time = time.time()