def default(self, obj): # print("obj.__class__", obj.__class__, "isinstance(obj.__class__, DeclarativeMeta)", isinstance(obj.__class__, DeclarativeMeta)) if isinstance(obj.__class__, DeclarativeMeta): # an SQLAlchemy class fields = {} for field in [ x for x in dir(obj) if not x.startswith('_') and x != 'metadata' ]: data = obj.__getattribute__(field) try: json.dumps( data ) # this will fail on non-encodable values, like other classes fields[field] = data except TypeError: # 添加了对datetime的处理 print(data) if isinstance(data, datetime): fields[field] = data.isoformat() elif isinstance(data, date): fields[field] = data.isoformat() elif isinstance(data, timedelta): fields[field] = (datetime.min + data).time().isoformat() else: fields[field] = None # a json-encodable dict return fields elif isinstance(obj, date): return json.dumps(date_2_str(obj)) return json.JSONEncoder.default(self, obj)
def get(self, status): """ 获取投资组合列表数据 """ args = paginate_parser.parse_args() page_no = args['page_no'] count = args['count'] user_id = session.get('user_id') # 获取各个组合 最新交易日 date_latest_query = db.session.query( PortfolioValueDaily.pl_id, func.max(PortfolioValueDaily.trade_date).label( 'trade_date_max')).group_by( PortfolioValueDaily.pl_id).subquery('date_latest') # 获取各个投资组合 最新净值 nav_latest_query = db.session.query( PortfolioValueDaily.pl_id, PortfolioValueDaily.trade_date, PortfolioValueDaily.rr, PortfolioValueDaily.nav).filter( PortfolioValueDaily.pl_id == date_latest_query.c.pl_id, PortfolioValueDaily.trade_date == date_latest_query.c.trade_date_max).subquery('nav_latest') # 分页查询投资组合信息及最新净值 if status == 'my': filter_c = PortfolioInfo.create_user_id == user_id elif status == 'all': filter_c = or_(PortfolioInfo.access_type == 'public', PortfolioInfo.create_user_id == user_id) elif status == 'star': # TODO: 星标投资组合 filter_c = not_(func.isnull(FavoriteCompare.update_time)) else: filter_c = None if filter_c is None: raise KeyError('status 参数错误 status = %s' % status) else: pagination = PortfolioInfo.query.outerjoin( nav_latest_query, PortfolioInfo.pl_id == nav_latest_query.c.pl_id).add_columns( nav_latest_query.c.trade_date, nav_latest_query.c.rr, nav_latest_query.c.nav, ).outerjoin(User).add_columns(User.username).outerjoin( FavoritePortfolio, and_(PortfolioInfo.pl_id == FavoritePortfolio.pl_id, FavoritePortfolio.user_id == user_id)).add_columns( func.if_( func.isnull(FavoritePortfolio.update_time), 0, 1).label('favorite')).filter( filter_c).paginate(page_no, count) logger.debug('%d / %d 页 %d / %d 条数据', pagination.page, pagination.pages, len(pagination.items), pagination.total) ret_dic_list = [{ 'pl_id': data.PortfolioInfo.pl_id, 'name': data.PortfolioInfo.name, 'date_from': date_2_str(data.PortfolioInfo.date_from), 'date_to': date_2_str(data.PortfolioInfo.date_to), 'status': data.PortfolioInfo.status, 'desc': data.PortfolioInfo.desc, 'create_user_id': data.PortfolioInfo.create_user_id, 'username': data.username, 'favorite': data.favorite, 'trade_date': date_2_str(data.trade_date), 'rr': try_2_float(data.rr), 'nav': try_2_float(data.nav), 'access_type': data.PortfolioInfo.access_type, } for data in pagination.items] ret_dic = { 'page': pagination.page, 'pages': pagination.pages, 'count': len(pagination.items), 'total': pagination.total, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next, 'data': ret_dic_list, } return ret_dic
def update_pl_compare_result(cmp_id, date_from, date_to, **kwargs): """ 对给定 cmp_id 更新指定日期段内的比较结果 PortfolioCompareInfo 中 params 格式 compare_method:'>', '<', 'between' compare_method 为 between时,asset1 与两个参考数据进行比较,否则与1个参考数据进行比较 compare_type:'rel.rr', 'abs.rr', 'abs.fix_point' compare_type 为 rel 与 资产或资产组合进行比较,abs 与数值进行比较; rr 代表与收益率进行比较,fix_point 代表与实际价格进行比较 :param cmp_id: :param date_from: :param date_to: :param kwargs: 便于被调用时接受其他冗余参数使用,暂时无用 :return: """ result_df = None pl_comp_info = PortfolioCompareInfo.query.get(cmp_id) if pl_comp_info.params is None or pl_comp_info.params == "": raise ValueError('cmp_id: %s 无效' % cmp_id) # 获取参数,计算日期范围交集 date_from = max([pl_comp_info.date_from, str_2_date(date_from)]) date_to = min([pl_comp_info.date_to, str_2_date(date_to)]) param_dic = json.loads(pl_comp_info.params) if 'date_start' in param_dic and len(param_dic['date_start']) >= 6: date_start = max([str_2_date(param_dic['date_start']), date_from]) else: date_start = max([pl_comp_info.date_from, date_from]) compare_method = param_dic['compare_method'] if compare_method not in ('>', '<', 'between'): raise ValueError("cmp_id: %s param_dic['compare_method']: %s 无效" % (cmp_id, param_dic['compare_method'])) compare_type = param_dic['compare_type'] if compare_type not in ('rel.rr', 'abs.rr', 'abs.fix_point'): raise ValueError("cmp_id: %s param_dic['compare_type']: %s 无效" % (cmp_id, param_dic['compare_type'])) has_value_asset = False # 判断是否存在 value 类型的资产 max_len_asset_n = None val_s_dic = {} need_repead_dic = {} # 获取相关数据,每条数据为一组 Series, 合并组成 df for asset_num in range( 1, 4 if compare_method == 'between' else 3): # 以后可能出现不止2个数值进行运算 asset_type_n_key = 'asset_type_' + str(asset_num) asset_type = param_dic[asset_type_n_key] # 用来从 param_dic 中取出相应的参数 asset_or_value_n_key = 'value_' + str( asset_num) if asset_type == 'value' else 'asset_' + str(asset_num) # 用来保持到计算结果 Series 中并对列进行命名,后续的比较计算均依赖于此名称 asset_n_key = 'asset_' + str(asset_num) bind_db, table_name = ASSET_TYPE_TABLE_NAME_DIC[asset_type] if table_name is None: has_value_asset = True val_s = pd.Series(name=asset_n_key, data=param_dic[asset_or_value_n_key]) need_repeat = True elif table_name == 'pl_value_daily': engine = db.get_engine( bind=bind_db) if bind_db is not None else db.engine sql_str = """SELECT trade_date, rr FROM pl_value_daily WHERE pl_id = %s AND trade_date BETWEEN %s AND %s ORDER BY trade_date""" val_s = pd.read_sql( sql_str, engine, params=[param_dic[asset_or_value_n_key], date_start, date_to], index_col='trade_date')['rr'].rename(asset_n_key) val_s = (val_s + 1).cumprod() need_repeat = False else: engine = db.get_engine( bind=bind_db) if bind_db is not None else db.engine sql_str = """select trade_date, close from {0} where wind_code = %s and trade_date between %s and %s ORDER BY trade_date""".format(table_name) val_s = pd.read_sql( sql_str, engine, params=[param_dic[asset_or_value_n_key], date_start, date_to], index_col='trade_date')['close'].rename(asset_n_key) need_repeat = False # 加入字典 val_s_dic[asset_n_key] = val_s need_repead_dic[asset_n_key] = need_repeat # 选择最长的一组数据进行记录,用于最后将 value型数据扩展长度 if not need_repeat: if max_len_asset_n is None: max_len_asset_n = asset_n_key elif len(val_s_dic[max_len_asset_n]) < len(val_s): max_len_asset_n = asset_n_key # 如果存在 value 型数据则扩展期长度与最长的一组数据一致 if has_value_asset: for asset_n_key, need_repeat in need_repead_dic.items(): if need_repeat: max_len_asset_val_s = val_s_dic[max_len_asset_n] asset_val = val_s_dic[asset_n_key].repeat( len(max_len_asset_val_s)) asset_val.index = max_len_asset_val_s.index val_s_dic[asset_n_key] = asset_val # 合并数据 data_df = pd.DataFrame(val_s_dic) # 只比较 pl_comp_info.date_from pl_comp_info.date_to 之间的数据 is_fit = (data_df.index >= pl_comp_info.date_from) & (data_df.index <= pl_comp_info.date_to) data_sub_df = data_df[is_fit] if data_sub_df.shape[0] == 0: return result_df if compare_method == '>': func = compare_func_larger elif compare_method == '<': func = compare_func_larger elif compare_method == 'between': func = compare_func_between else: raise ValueError("cmp_id: %s param_dic['compare_method']: %s 暂不支持" % (cmp_id, param_dic['compare_method'])) calc_result_df = pd.DataFrame([ pd.Series( func(y), name=x, index=['result', 'shift_value', 'shift_rate']) for x, y in data_sub_df.T.items() ], index=data_sub_df.index) result_df = data_sub_df.merge(calc_result_df, left_index=True, right_index=True) if result_df.shape[0] == 0: return None # data_sub_df['result'] = data_sub_df.apply(func, axis=1) result_df['cmp_id'] = cmp_id result_df.index.rename('trade_date', inplace=True) # 删除重复数据 result_date_min = result_df.index.min() result_date_max = result_df.index.max() db.session.query(PortfolioCompareResult).filter( PortfolioCompareResult.cmp_id == cmp_id, PortfolioCompareResult.trade_date >= result_date_min, PortfolioCompareResult.trade_date <= result_date_max).delete() db.session.commit() # 插入数据 result_df.to_sql('pl_compare_result', db.engine, if_exists='append') logger.debug('更新 cmp_id:%d %s - %s %d 条记录', cmp_id, date_2_str(result_date_min), date_2_str(result_date_max), result_df.shape[0]) return result_date_max
def get(self, _id, status): """ 投资组合资产分布比例 """ logger.info('pl_id=%s, status=%s', _id, status) if status == 'latest': result_list = db.session.query( PortfolioData.trade_date, PortfolioData.asset_type, func.sum(PortfolioData.weight).label('weight')).group_by( PortfolioData.trade_date, PortfolioData.asset_type).filter( and_( PortfolioData.trade_date == (db.session.query( func.max(PortfolioData.trade_date)).filter( PortfolioData.pl_id == _id)), PortfolioData.pl_id == _id)).all() elif status == 'recent': # count = request.args.get('count', 5, type=int) args = paginate_parser.parse_args() count = args['count'] data_list = [ date_2_str(d[0]) for d in db.session.query(PortfolioData.trade_date).filter( PortfolioData.pl_id == _id).group_by( PortfolioData.trade_date).limit(count).all() ] result_list = db.session.query( PortfolioData.trade_date, PortfolioData.asset_type, func.sum(PortfolioData.weight).label('weight')).group_by( PortfolioData.trade_date, PortfolioData.asset_type).filter( and_(PortfolioData.trade_date.in_(data_list), PortfolioData.pl_id == _id)).all() elif status == 'all': result_list = db.session.query( PortfolioData.trade_date, PortfolioData.asset_type, func.sum(PortfolioData.weight).label('weight')).group_by( PortfolioData.trade_date, PortfolioData.asset_type).all() else: raise KeyError('status 参数错误 status = %s' % status) # 合并数据结果 ret_dic_list = [] ret_dic_dic = {} for data in result_list: trade_date = data.trade_date if trade_date in ret_dic_dic: pl_list, asset_name_list = ret_dic_dic[trade_date] else: pl_list = [] asset_name_list = [] ret_dic_dic[trade_date] = (pl_list, asset_name_list) ret_dic_list.append({ 'trade_date': date_2_str(trade_date), 'data': pl_list, 'name_list': asset_name_list }) # 扩展 pl_list.append({ 'name': data.asset_type, 'value': try_2_float(data.weight), }) asset_name_list.append(data.asset_type) ret_dic = { 'count': len(ret_dic_list), 'data': ret_dic_list, } # logger.debug("ret_dic:%s", ret_dic) return ret_dic
def get_pl_data_list_by_date(_id, status, page_no, count): """ 获取制定投资组合的成分及权重数据(分页) status :param _id: :param status: latest 最近一次调仓数据, recent 最近几日调仓数据,日期逆序排列 :return: """ # logger.info('pl_id=%s, status=%s', _id, status) # PortfolioData.query.filter(PortfolioData.id == pl_id, PortfolioData.trade_date == 1) if status == 'latest': date_cur = db.session.query(func.max( PortfolioData.trade_date)).filter( PortfolioData.pl_id == _id).scalar() # 最新调仓日期 pagination = PortfolioData.query.filter( PortfolioData.pl_id == _id, PortfolioData.trade_date == date_cur).paginate(page_no, count) elif status == 'recent': pagination = PortfolioData.query.group_by( PortfolioData.trade_date).filter( PortfolioData.pl_id == _id).order_by( PortfolioData.trade_date.desc()).paginate( page_no, count) else: raise KeyError('status = %s 不支持' % status) logger.debug('%d / %d 页 %d / %d 条数据', pagination.page, pagination.pages, len(pagination.items), pagination.total) date_list = [data.trade_date for data in pagination.items] date_grouped_dic = {} ret_dic_list = [] if len(date_list) > 0: # date_grouped_dic 中的 value 为 data_list 实际与 ret_dic_list 中对应日期的 data_list 为同一对象 # 因此可以直接修改 items = db.session.query(PortfolioData).filter( PortfolioData.pl_id == _id, PortfolioData.trade_date.in_(date_list)).all() for data in items: date_cur = date_2_str(data.trade_date) if date_cur in date_grouped_dic: data_list = date_grouped_dic[date_cur] else: data_list = [] date_grouped_dic[date_cur] = data_list ret_dic_list.append({ 'trade_date': date_cur, 'data': data_list }) # 获取资产及资产类别中文名称 asset_name = get_asset_name(data.asset_type, data.asset_code) # 因为list是引用性数据,直接放入 ret_dic_list # 对 data_list 的修改直接反应到 ret_dic_list 中去 data_list.append({ 'id': data.id, 'asset_code': data.asset_code, 'asset_name': asset_name, 'asset_type': data.asset_type, 'trade_date': date_cur, 'weight': try_2_float(data.weight), 'weight_before': try_2_float(data.weight_before), 'price_type': data.price_type, 'direction': data.direction, }) ret_dic = { 'page': pagination.page, 'pages': pagination.pages, 'count': len(pagination.items), 'total': pagination.total, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next, 'data': ret_dic_list, } # logger.debug(ret_dic) return ret_dic
def get_pl_data_list(_id, status, page_no, count): """ 获取制定投资组合的成分及权重数据(分页) :param _id: :param status: :param page_no: :param count: :return: """ if status == 'latest': pagination = PortfolioData.query.filter( PortfolioData.pl_id == _id, PortfolioData.trade_date == (db.session.query( func.max(PortfolioData.trade_date)).filter( PortfolioData.pl_id == _id))).paginate(page_no, count) elif status == 'recent': pagination = PortfolioData.query.filter( PortfolioData.pl_id == _id).order_by( PortfolioData.trade_date.desc()).paginate(page_no, count) else: raise KeyError('status = %s 不支持' % status) logger.debug('%d / %d 页 %d / %d 条数据', pagination.page, pagination.pages, len(pagination.items), pagination.total) date_grouped_dic = {} ret_dic_list = [] for data in pagination.items: date_cur = date_2_str(data.trade_date) if date_cur in date_grouped_dic: data_list = date_grouped_dic[date_cur] else: data_list = [] date_grouped_dic[date_cur] = data_list ret_dic_list.append({ 'trade_date': date_cur, 'data': data_list }) # 获取资产及资产类别中文名称 asset_name = get_asset_name(data.asset_type, data.asset_code) # 因为list是引用性数据,直接放入 ret_dic_list # 对 data_list 的修改直接反应到 ret_dic_list 中去 data_list.append({ 'id': data.id, 'asset_code': data.asset_code, 'asset_name': asset_name, 'asset_type': data.asset_type, 'trade_date': date_cur, 'weight': try_2_float(data.weight), 'weight_before': try_2_float(data.weight_before), 'price_type': data.price_type, 'direction': data.direction, }) ret_dic = { 'page': pagination.page, 'pages': pagination.pages, 'count': len(pagination.items), 'total': pagination.total, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next, 'data': ret_dic_list, } # logger.debug(ret_dic) return ret_dic
def get(self, status): """ 获取比较列表数据(分页) """ args = paginate_parser.parse_args() page_no = args['page_no'] count = args['count'] user_id = session.get('user_id') logger.debug('get_cmp_list user_id:%s', user_id) if status == 'my': filter_c = PortfolioCompareInfo.create_user_id == user_id having_c = None elif status == 'all': filter_c = or_(PortfolioCompareInfo.create_user_id == user_id, PortfolioCompareInfo.access_type == 'public') having_c = None elif status == 'star': filter_c = and_( or_(PortfolioCompareInfo.create_user_id == user_id, PortfolioCompareInfo.access_type == 'public'), not_(func.isnull(FavoriteCompare.update_time))) having_c = None elif status == 'verified': filter_c = or_(PortfolioCompareInfo.create_user_id == user_id, PortfolioCompareInfo.access_type == 'public') having_c = column('complete_rate') >= 1 elif status == 'unverified': filter_c = or_(PortfolioCompareInfo.create_user_id == user_id, PortfolioCompareInfo.access_type == 'public') having_c = or_( column('complete_rate').is_(None), column('complete_rate') < 1) else: raise KeyError('status 参数错误 status = %s' % status) # 整理数据 # logger.debug("data_list_df len:%d", data_list_df.shape[0]) # data_list_df = data_list_df.where(data_list_df.notna(), None) # data_list = data_list_df.to_dict('record') # data_table_dic = {'data': data_list} # logger.debug(data_table_dic) query = PortfolioCompareInfo.query.outerjoin( PortfolioCompareResult ).group_by(PortfolioCompareResult.cmp_id).add_columns( func.count().label('tot_count'), func.min( PortfolioCompareResult.trade_date).label('trade_date_min'), func.max( PortfolioCompareResult.trade_date).label('trade_date_max'), func.sum(PortfolioCompareResult.result).label('fit_count'), (func.sum(PortfolioCompareResult.result) / func.count()).label('fit_rate'), ((func.max(PortfolioCompareResult.trade_date) - PortfolioCompareInfo.date_from) / (PortfolioCompareInfo.date_to - PortfolioCompareInfo.date_from) ).label('complete_rate')).outerjoin(User).add_columns( User.username).outerjoin( FavoriteCompare, and_( PortfolioCompareInfo.cmp_id == FavoriteCompare.cmp_id, FavoriteCompare.user_id == user_id)).add_columns( func.if_( func.isnull(FavoriteCompare.update_time), 0, 1).label('favorite')).filter( and_(filter_c, PortfolioCompareInfo.is_del == 0)) if having_c is None: pagination = query.paginate(page_no, count) else: pagination = query.having(having_c).paginate(page_no, count) logger.debug('%d / %d 页 %d / %d 条数据', pagination.page, pagination.pages, len(pagination.items), pagination.total) ret_dic_list = [{ 'cmp_id': data.PortfolioCompareInfo.cmp_id, 'name': data.PortfolioCompareInfo.name, 'status': data.PortfolioCompareInfo.status, 'params': data.PortfolioCompareInfo.params, 'desc': data.PortfolioCompareInfo.desc, 'date_from': date_2_str(data.PortfolioCompareInfo.date_from), 'date_to': date_2_str(data.PortfolioCompareInfo.date_to), 'trade_date_min': date_2_str(data.trade_date_min), 'trade_date_max': date_2_str(data.trade_date_max), 'create_user_id': data.PortfolioCompareInfo.create_user_id, 'username': data.username, 'favorite': data.favorite, 'complete_rate': try_2_float(data.complete_rate), } for data in pagination.items] ret_dic = { 'page': pagination.page, 'pages': pagination.pages, 'count': len(pagination.items), 'total': pagination.total, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next, 'data': ret_dic_list, } return ret_dic
def get(self, _id): """ 获取投资组合的绩效及统计信息 """ ret_data = {"general": {}, "performance": {}, "others": {}} pl_obj = db.session.query(PortfolioInfo, User.username).filter( PortfolioInfo.pl_id == _id, User.id == PortfolioInfo.create_user_id).first() ret_data['general']['pl_id'] = pl_obj.PortfolioInfo.pl_id ret_data['general']['name'] = pl_obj.PortfolioInfo.name ret_data['general']['date_from'] = date_2_str( pl_obj.PortfolioInfo.date_from) ret_data['general']['date_to'] = date_2_str( pl_obj.PortfolioInfo.date_to) ret_data['general'][ 'create_user_id'] = pl_obj.PortfolioInfo.create_user_id ret_data['general']['create_user_name'] = pl_obj.username ret_data['general']['created_at'] = datetime_2_str( pl_obj.PortfolioInfo.create_dt) ret_data['general']['access_type'] = pl_obj.PortfolioInfo.access_type ret_data['general']['status'] = pl_obj.PortfolioInfo.status ret_data['general']['is_del'] = pl_obj.PortfolioInfo.is_del ret_data['general']['desc'] = pl_obj.PortfolioInfo.desc # date_latest = db.session.query(func.max(PortfolioValueDaily.trade_date)).filter( # PortfolioValueDaily.pl_id == _id).scalar() pl_df = pd.read_sql("select trade_date, nav from " + PortfolioValueDaily.__tablename__ + " where pl_id = %s order by trade_date", db.engine, params=[_id], index_col='trade_date') if pl_df.shape[0] > 0: stat_dic_dic = calc_performance(pl_df, freq=None) if 'nav' in stat_dic_dic: performance_dic = stat_dic_dic['nav'] for key in list(performance_dic.keys()): value = performance_dic[key] if value is None: performance_dic[key] = '-' elif type(value) is date: performance_dic[key] = date_2_str(value) elif type(value) is not str and not np.isfinite(value): performance_dic[key] = '-' ret_data['performance'] = stat_dic_dic['nav'] else: logger.warning('stat_dic_dic:\n%s', stat_dic_dic) # 星标数量 star_count = db.session.query( func.count()).filter(FavoritePortfolio.pl_id == _id).scalar() ret_data['others']['star_count'] = int(star_count) # 最新净值 latest_nav_sub_query = db.session.query( PortfolioValueDaily.nav).filter( PortfolioValueDaily.pl_id == _id, PortfolioValueDaily.trade_date == (db.session.query( func.max(PortfolioValueDaily.trade_date)).filter( PortfolioValueDaily.pl_id == _id))).subquery() # 收益率排名 max_trade_date_sub_query = db.session.query( PortfolioValueDaily.pl_id, func.max(PortfolioValueDaily.trade_date).label( 'trade_date_max')).group_by( PortfolioValueDaily.pl_id).subquery() latest_nav_list_sub_query = db.session.query( PortfolioValueDaily.pl_id, PortfolioValueDaily.nav).join( max_trade_date_sub_query, and_( PortfolioValueDaily.pl_id == max_trade_date_sub_query.c.pl_id, PortfolioValueDaily.trade_date == max_trade_date_sub_query.c.trade_date_max)).order_by( PortfolioValueDaily.nav.asc()).subquery() # 净值排名 nav_rank_count = db.session.query(func.count()).filter( latest_nav_list_sub_query.c.nav >= latest_nav_sub_query.c.nav ).scalar() # 总产品数量 pl_count_has_nav = db.session.query( func.count( db.session.query(PortfolioValueDaily.pl_id).group_by( PortfolioValueDaily.pl_id).subquery().c.pl_id)).scalar() ret_data['others']['rank'] = 0 if pl_count_has_nav == 0 else float( nav_rank_count / pl_count_has_nav) return ret_data