import baostock as bs
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
def get_bs_df(code, list_para, start_date, end_date, frequency='d', adjustflag='3'):
para = ','.join(list_para)
rs = bs.query_history_k_data_plus(code,para,
start_date=start_date, end_date=end_date,
frequency=frequency, adjustflag=adjustflag)
data_list = []
while (rs.error_code == '0') & rs.next():
data_list.append(rs.get_row_data())
df_result = pd.DataFrame(data_list, columns=rs.fields)
print(f'get {len(df_result)} records!')
return df_result
def get_bs_profit_data(code, year, quarter):
profit_list = []
rs_profit = bs.query_profit_data(code=code, year=year, quarter=quarter)
while (rs_profit.error_code == '0') & rs_profit.next():
profit_list.append(rs_profit.get_row_data())
df_result = pd.DataFrame(profit_list, columns=rs_profit.fields)
return df_result
lg = bs.login()
login success!
code = 'sh.600000'
list_para = ['date', 'code', 'close', 'volume', 'turn', 'peTTM', 'psTTM']
start_date = '2020-01-01'
end_date = '2024-01-20'
df_daily = get_bs_df(code, list_para, start_date, end_date)
df_profit = get_bs_profit_data(code, '2020', '1')
get 984 records!
df_profit
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
# get market value and earning
df_daily['market_value'] = df_daily['close'].astype(float) * float(df_profit['liqaShare'].iloc[0])
df_daily['peTTM'] = df_daily['peTTM'].replace('', pd.NaT)
df_daily['peTTM'] = df_daily['peTTM'].astype(float)
df_daily['peTTM'] = df_daily['peTTM'].fillna((df_daily['peTTM'].shift() + df_daily['peTTM'].shift(-1)) / 2)
df_daily['earning'] = df_daily['market_value'] / df_daily['peTTM'].astype(float)
df_daily
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
df_daily['market_value'].plot()
<Axes: >
df_daily['earning'].plot()
<Axes: >
neutralize the market value
groups = df_daily.groupby('date')
groups
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000248B0E74470>
# 定义一个函数来执行市值中性化
def market_neutralize(group):
# 提取市值和收益的数据列
market_cap = group['market_value']
returns = group['earning']
# 添加截距项
X = sm.add_constant(market_cap)
# 执行线性回归,拟合收益率与市值的关系
model = sm.OLS(returns, X)
results = model.fit()
# 提取回归系数
beta = results.params['market_value']
# 计算市值中性化后的收益
neutralized_returns = returns - beta * market_cap
# 将市值中性化后的收益添加到数据框中
group['neutralized_value'] = neutralized_returns
return group
neutralized_data = groups.apply(market_neutralize)
C:\Users\livid\AppData\Local\Temp\ipykernel_26144\1156377993.py:1: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
neutralized_data = groups.apply(market_neutralize)
neutralized_data
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
neutralized_data['neutralized_value'].plot()
<Axes: xlabel='date,None'>
def get_all_stock(day):
rs = bs.query_all_stock(day=day)
print('login respond error_msg:'+lg.error_msg)
data_list = []
while (rs.error_code == '0') & rs.next():
data_list.append(rs.get_row_data())
df_result = pd.DataFrame(data_list, columns=rs.fields)
return df_result
df_all_stock = get_all_stock('2023-06-30')
df_all_stock
login respond error_msg:success
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
df_all_stock
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
random_code_list = df_all_stock['code'].sample(n=100, random_state=42)
random_code_list[:10]
4200 sz.300042
4523 sz.300382
5039 sz.300915
5271 sz.301176
4570 sz.300429
80 bj.833533
4793 sz.300657
5444 sz.399001
2275 sh.688195
4889 sz.300759
Name: code, dtype: object
df_random_stock = pd.DataFrame()
for code in random_code_list:
df_query_profit = get_bs_profit_data(code, '2023', '1')
df_random_stock = pd.concat([df_query_profit, df_random_stock])
df_random_stock
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
for col in ['roeAvg', 'npMargin', 'gpMargin', 'epsTTM']:
df_random_stock[col] = df_random_stock[col].replace('', float('nan'))
df_random_stock[col] = df_random_stock[col].astype(float)
df_random_stock[col] = df_random_stock[col].fillna((df_random_stock[col].shift() + df_random_stock[col].shift(-1)) / 2)
df_random_stock[col] = df_random_stock[col].ffill().bfill()
df_random_stock.info()
<class 'pandas.core.frame.DataFrame'>
Index: 82 entries, 0 to 0
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 code 82 non-null object
1 pubDate 82 non-null object
2 statDate 82 non-null object
3 roeAvg 82 non-null float64
4 npMargin 82 non-null float64
5 gpMargin 82 non-null float64
6 netProfit 82 non-null object
7 epsTTM 82 non-null float64
8 MBRevenue 82 non-null object
9 totalShare 82 non-null object
10 liqaShare 82 non-null object
dtypes: float64(4), object(7)
memory usage: 7.7+ KB
X_test
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
X = df_random_stock[['roeAvg', 'npMargin', 'gpMargin']] # 特征因子
y = df_random_stock['epsTTM'] # 目标变量
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = pd.DataFrame(scaler.transform(X_test))
model = LinearRegression()
model.fit(X_train, y_train)
print('Factor weights:', model.coef_)
y_pred = model.predict(X_test)
predicted_returns = pd.DataFrame({
'Stock': X_test.index,
'Predicted return': y_pred
})
selected_stocks = predicted_returns[predicted_returns['Predicted return'] > 0.1]
print('Selected stocks:', selected_stocks)
Factor weights: [0.5816784 0.2429101 0.27459906]
Selected stocks: Stock Predicted return
0 0 0.550256
1 1 0.391729
2 2 0.451719
3 3 0.537539
4 4 0.390086
5 5 0.535062
6 6 0.728756
7 7 0.748340
8 8 0.329657
9 9 0.166179
10 10 0.868106
11 11 0.664897
12 12 1.249381
13 13 1.205148
14 14 0.881246
15 15 1.089587
16 16 0.447703
y_pred
array([0.55025615, 0.39172872, 0.45171871, 0.5375395 , 0.39008628,
0.5350618 , 0.72875629, 0.74833969, 0.32965658, 0.16617911,
0.86810565, 0.66489708, 1.24938099, 1.20514782, 0.88124623,
1.08958659, 0.44770297])
pd.Series(y_test)
0 0.209028
0 0.069248
0 1.690382
0 0.278836
0 0.010221
0 0.438135
0 0.462125
0 0.789891
0 0.773333
0 0.053418
0 -0.210894
0 0.056950
0 2.125819
0 5.535384
0 0.398913
0 0.309732
0 0.421868
Name: epsTTM, dtype: float64
pd.DataFrame({'y_pred':y_pred, 'y_test':y_test, 'diff %':(y_pred-y_test)/y_test*100})
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
ps 看得出线性回归这样预测的效果并不理想