基于周期因子的资金流入流出预测
周期因子方法(简要介绍)
基本思想:把时间序列拆成“基数”(长期水平/趋势)和“周期波动”两部分。周期因子用于刻画固定周期内的相对涨落(如一周内、一个月内不同天的规律),通过因子对基数进行放大/缩小来得到每天的预测。
核心步骤:
数据聚合:按天聚合得到每日申购、赎回总额,并构造 weekday(0-6) 与 day_of_month(1-31)。
计算基数:通常取历史总体均值或平滑均值(如7日均线)作为基数。
计算周期因子:
weekday 因子 = 各 weekday 的历史均值 / 全局均值
day 因子 = 各日号的历史均值 / 全局均值
为避免偏置,建议让因子组均值约等于 1(天然如此,或做归一化)。
组合方式(叠加/相互作用):
乘法:factor = weekday_factor × day_factor(放大交互效果,易激进)
几何平均:factor = sqrt(weekday_factor × day_factor)(更稳健)
加权平均:factor = w1×weekday_factor + w2×day_factor(可调权重)
预测生成:预测值 = 基数 × 组合周期因子。可按未来日期映射到对应 weekday 与 day_of_month,从而逐日生成。
可选趋势:若存在缓慢上升/下降趋势,可用移动平均/线性趋势外推更新“基数”,再乘周期因子。
关键细节与建议:
稀疏天处理:部分 day_of_month 在短月缺失,建议用相邻日或全局均值补齐,或只在目标月份的天集合上学习因子。
稳定化:对异常波动做 winsorize/中位数/指数平滑,避免因子被极端值带偏。
数据泄漏:仅用可获得的历史区间估计因子与基数;滚动重估更安全。
正则化:将因子收缩向 1(如因子 = 1 + α×(原因子-1))以降低过拟合。
评估分层:分别评估基数与周期因子贡献,观察不同组合方式(乘法/几何平均/加权)对误差的影响。
与机器学习融合:将 weekday、day_of_month 的 one-hot 或 sin/cos 作为模型特征;或先用 ML 预测“基数”,再用周期因子做后处理(post-adjustment)。
何时选择哪种组合:
周期效应强且相互放大明显:乘法
期望稳健、抗极值:几何平均
需要灵活调参:加权平均(权重可用验证集调优)
最小可用实现(逻辑回顾):
基数 = 历史均值
weekday 因子、day 因子 = 该组均值/总体均值
组合因子 = 几何平均或乘法
未来每天:按日期映射 weekday/day,预测 = 基数 × 组合因子
尝试几次后,本项目取历史均值作为基数,采用因子相乘的方法效果更好,能达到120分以上。
代码实战
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')# 解决中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号print("开始加载数据...")# 读取数据
df = pd.read_csv('data/user_balance_table.csv', encoding='utf-8')# 转换 report_date 为日期格式
df['report_date'] = pd.to_datetime(df['report_date'], format='%Y%m%d')# 截取 2014-03 到 2014-08 的数据
df = df[df['report_date'] >= '2014-03-01']
df = df[df['report_date'] <= '2014-08-31']# 按照日期聚合数据
daily_data = df.groupby('report_date')[['total_purchase_amt', 'total_redeem_amt']].sum().reset_index()print(f"数据加载完成,共 {len(daily_data)} 天数据")
print(f"数据时间范围:{daily_data['report_date'].min()} 到 {daily_data['report_date'].max()}")# 1. 创建时间特征
def create_time_features(df):"""创建时间特征"""df = df.copy()# 基础时间特征df['weekday'] = df['report_date'].dt.dayofweek # 0=周一, 6=周日df['day_of_month'] = df['report_date'].dt.day # 1-31# df['month'] = df['report_date'].dt.month# df['day_of_year'] = df['report_date'].dt.dayofyear# # 是否为月初/月末# df['is_month_start'] = df['report_date'].dt.is_month_start.astype(int)# df['is_month_end'] = df['report_date'].dt.is_month_end.astype(int)# # 是否为周末# df['is_weekend'] = (df['weekday'] >= 5).astype(int)return df# 创建特征
daily_data = create_time_features(daily_data)# 3. 计算周期系数(非标准计算方法)
def calculate_cycle_coefficients(df):"""计算周期系数"""# 计算整体均值作为基数overall_purchase_mean = df['total_purchase_amt'].mean()overall_redeem_mean = df['total_redeem_amt'].mean()print(f"\n=== 基数计算 ===")print(f"申购金额整体均值(基数): {overall_purchase_mean:,.2f}")print(f"赎回金额整体均值(基数): {overall_redeem_mean:,.2f}")# 计算星期几周期系数weekday_purchase = df.groupby('weekday')['total_purchase_amt'].mean()weekday_redeem = df.groupby('weekday')['total_redeem_amt'].mean()weekday_purchase_coef = weekday_purchase / overall_purchase_meanweekday_redeem_coef = weekday_redeem / overall_redeem_mean# 计算月份中日期周期系数day_purchase = df.groupby('day_of_month')['total_purchase_amt'].mean()day_redeem = df.groupby('day_of_month')['total_redeem_amt'].mean()day_purchase_coef = day_purchase / overall_purchase_meanday_redeem_coef = day_redeem / overall_redeem_meanreturn {'weekday_purchase_coef': weekday_purchase_coef,'weekday_redeem_coef': weekday_redeem_coef,'day_purchase_coef': day_purchase_coef,'day_redeem_coef': day_redeem_coef,'overall_purchase_mean': overall_purchase_mean,'overall_redeem_mean': overall_redeem_mean}# 计算周期系数
coefficients = calculate_cycle_coefficients(daily_data)# 4. 显示周期系数结果
print("\n=== 周期系数计算结果 ===")
print("星期几申购周期系数:")
for i, coef in coefficients['weekday_purchase_coef'].items():weekday_name = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][i]print(f" {weekday_name}: {coef:.3f}")print("\n星期几赎回周期系数:")
for i, coef in coefficients['weekday_redeem_coef'].items():weekday_name = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][i]print(f" {weekday_name}: {coef:.3f}")print("\n月份中日期申购周期系数(前10天):")
for day in range(1, 11):coef = coefficients['day_purchase_coef'].get(day, 1.0)print(f" 第{day}天: {coef:.3f}")# 5. 可视化周期系数
plt.figure(figsize=(15, 10))# 星期几周期系数
plt.subplot(2, 2, 1)
weekday_names = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
plt.bar(weekday_names, coefficients['weekday_purchase_coef'].values, alpha=0.7, color='blue', label='申购')
plt.bar(weekday_names, coefficients['weekday_redeem_coef'].values, alpha=0.7, color='red', label='赎回')
plt.title('星期几周期系数', fontsize=12, fontweight='bold')
plt.xlabel('星期')
plt.ylabel('周期系数')
plt.legend()
plt.grid(True, alpha=0.3)# 月份中日期周期系数
plt.subplot(2, 2, 2)
day_range = range(1, 32)
day_purchase_coef_values = [coefficients['day_purchase_coef'].get(day, 1.0) for day in day_range]
day_redeem_coef_values = [coefficients['day_redeem_coef'].get(day, 1.0) for day in day_range]
plt.plot(day_range, day_purchase_coef_values, marker='o', linewidth=2, color='blue', label='申购')
plt.plot(day_range, day_redeem_coef_values, marker='s', linewidth=2, color='red', label='赎回')
plt.title('月份中日期周期系数', fontsize=12, fontweight='bold')
plt.xlabel('日期')
plt.ylabel('周期系数')
plt.legend()
plt.grid(True, alpha=0.3)# 周期系数分布
plt.subplot(2, 2, 3)
plt.hist(coefficients['weekday_purchase_coef'].values, bins=7, alpha=0.7, color='blue', label='申购')
plt.hist(coefficients['weekday_redeem_coef'].values, bins=7, alpha=0.7, color='red', label='赎回')
plt.title('星期几周期系数分布', fontsize=12, fontweight='bold')
plt.xlabel('周期系数')
plt.ylabel('频次')
plt.legend()
plt.grid(True, alpha=0.3)# 月份周期系数分布
plt.subplot(2, 2, 4)
plt.hist(day_purchase_coef_values, bins=15, alpha=0.7, color='blue', label='申购')
plt.hist(day_redeem_coef_values, bins=15, alpha=0.7, color='red', label='赎回')
plt.title('月份中日期周期系数分布', fontsize=12, fontweight='bold')
plt.xlabel('周期系数')
plt.ylabel('频次')
plt.legend()
plt.grid(True, alpha=0.3)plt.tight_layout()
plt.show()# 6. 预测未来30天(使用乘法加成)
def predict_future_values(start_date, days=30):"""使用乘法加成预测未来值"""future_dates = [start_date + timedelta(days=i) for i in range(days)]future_df = pd.DataFrame({'report_date': future_dates})future_df['weekday'] = future_df['report_date'].dt.dayofweek # 0=周一,6=周日future_df['day_of_month'] = future_df['report_date'].dt.daypredictions = []for i, row in future_df.iterrows():weekday = row['weekday']day_of_month = row['day_of_month']# 获取周期系数weekday_purchase_coef = coefficients['weekday_purchase_coef'].get(weekday, 1.0)weekday_redeem_coef = coefficients['weekday_redeem_coef'].get(weekday, 1.0)day_purchase_coef = coefficients['day_purchase_coef'].get(day_of_month, 1.0)day_redeem_coef = coefficients['day_redeem_coef'].get(day_of_month, 1.0)# 乘法加成计算(星期系数 × 日期系数 × 基数)purchase_pred = coefficients['overall_purchase_mean'] * weekday_purchase_coef * day_purchase_coefredeem_pred = coefficients['overall_redeem_mean'] * weekday_redeem_coef * day_redeem_coefpredictions.append({'report_date': row['report_date'].strftime('%Y%m%d'),'purchase': max(purchase_pred, 0), # 确保非负'redeem': max(redeem_pred, 0), # 确保非负'weekday_coef_purchase': weekday_purchase_coef,'day_coef_purchase': day_purchase_coef,'weekday_coef_redeem': weekday_redeem_coef,'day_coef_redeem': day_redeem_coef})return predictions# 预测未来30天
print("\n开始预测未来30天...")
start_date = datetime(2014, 9, 1)
predictions = predict_future_values(start_date, 30)# 7. 结果整理
result_df = pd.DataFrame(predictions)# 保存结果
result_df.to_csv('periodic_factor_prediction_2.csv', index=False, encoding='utf-8')print("预测完成!结果已保存到 periodic_factor_prediction_result_20140901_20140930.csv")# 8. 可视化预测结果
plt.figure(figsize=(15, 10))# 预测结果
plt.subplot(2, 2, 1)
plt.plot(range(30), result_df['purchase'], marker='o', linewidth=2, color='blue', label='预测申购金额')
plt.title('2014年9月申购金额预测(周期系数乘法加成)', fontsize=12, fontweight='bold')
plt.xlabel('日期(从9月1日开始)')
plt.ylabel('申购金额')
plt.legend()
plt.grid(True, alpha=0.3)plt.subplot(2, 2, 2)
plt.plot(range(30), result_df['redeem'], marker='s', linewidth=2, color='red', label='预测赎回金额')
plt.title('2014年9月赎回金额预测(周期系数乘法加成)', fontsize=12, fontweight='bold')
plt.xlabel('日期(从9月1日开始)')
plt.ylabel('赎回金额')
plt.legend()
plt.grid(True, alpha=0.3)# 显示前10天预测结果
print("\n=== 未来30天预测结果(前10天) ===")
print(result_df[['report_date', 'purchase', 'redeem']].head(10))# 显示周期系数信息
print("\n=== 周期系数信息(前10天) ===")
print("日期\t\t申购星期系数\t申购日期系数\t赎回星期系数\t赎回日期系数")
for i in range(10):row = result_df.iloc[i]print(f"{row['report_date']}\t{row['weekday_coef_purchase']:.3f}\t\t{row['day_coef_purchase']:.3f}\t\t{row['weekday_coef_redeem']:.3f}\t\t{row['day_coef_redeem']:.3f}")print("\n=== 完整预测结果 ===")
print(result_df[['report_date', 'purchase', 'redeem']].to_string(index=False))print("\n预测完成!模型使用周期系数乘法加成方法:")
print("1. 识别了星期周期和月份周期")
print("2. 计算了周期系数(相对于均值的比率)")
print("3. 使用均值作为基数")
print("4. 通过乘法加成计算最终值:基数 × 星期系数 × 日期系数")