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: >

png

df_daily['earning'].plot()
<Axes: >

png

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'>

png

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 看得出线性回归这样预测的效果并不理想