forked from jackluson/convertible-bond-crawler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilter.py
303 lines (262 loc) · 12.1 KB
/
filter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
'''
Desc:
File: /filter.py
File Created: Sunday, 9th April 2023 4:10:19 pm
Author: [email protected]
-----
Copyright (c) 2022 Camel Lu
'''
from datetime import datetime
def filter_profit_due(df):
df_filter = df.loc[(df['rate_expire_aftertax'] > 0)
# & (df['price'] < 115)
& (df['date_convert_distance'] == '已到')
& (df['cb_to_pb'] > 1.5)
& (df['is_repair_flag'] == 'True')
# & (df['remain_to_cap'] > 5)
]
def my_filter(row):
if '暂不行使下修权利' in row.repair_flag_remark or '距离不下修承诺' in row.repair_flag_remark:
return False
return True
df_filter = df_filter[df_filter.apply(my_filter, axis=1)]
return df_filter
def filter_return_lucky(df):
df_filter = df.loc[(df['price'] < 125)
& (df['rate_expire_aftertax'] > -10)
& (df['date_return_distance'] == '回售内')
& (~df["cb_name"].str.contains("EB"))
& (df['cb_to_pb'] > (1 + df['premium_rate'] * 0.008))
& (df['is_repair_flag'] == 'True')
& (df['remain_to_cap'] > 5)
]
df_filter = df_filter.sort_values(
by='new_style', ascending=True, ignore_index=True)
return df_filter
def filter_double_low(df):
df_filter = df.loc[(df['date_return_distance'] != '无权')
& (~df["cb_name"].str.contains("EB"))
& (df['is_unlist'] == 'N')
# & (df['date_return_distance'] != '回售内')
& (df['is_ransom_flag'] == 'False')
& (df['cb_to_pb'] > 0.5)
& (df['remain_to_cap'] > 5)
& (((df['price'] < 128)
& (df['premium_rate'] < 10)) | ((df['price'] < 125)
& (df['premium_rate'] < 15)))
]
def due_filter(row):
if '天' in row.date_remain_distance and not '年' in row.date_remain_distance:
day_count = float(row.date_remain_distance[0:-1])
return day_count > 90
return True
df_filter = df_filter[df_filter.apply(due_filter, axis=1)]
df_filter = df_filter.sort_values(
by='new_style', ascending=True, ignore_index=True)
return df_filter
def filter_three_low(df):
df_filter = df.loc[
(~df["cb_name"].str.contains("EB"))
# & (df['date_return_distance'] != '回售内')
& (df['is_ransom_flag'] == 'False')
& (df['cb_to_pb'] > 0.5)
& (df['remain_amount'] < 2)
& ((df['premium_rate'] < 30) | (df['price'] < 130))
& (df['market_cap'] < 100)
]
def due_filter(row):
if '天' in row.date_remain_distance and not '年' in row.date_remain_distance:
day_count = float(row.date_remain_distance[0:-1])
return day_count > 90
return True
df_filter = df_filter[df_filter.apply(due_filter, axis=1)]
df_filter = df_filter.sort_values(
by='remain_amount', ascending=True, ignore_index=True)
return df_filter
def filter_disable_converte(df):
df_filter = df.loc[
(~df["cb_name"].str.contains("EB"))
& (df['date_convert_distance'] != '已到')
& (df['is_unlist'] == 'N')
& (df['last_is_unlist'] == 'N')
]
df_filter = df_filter.sort_values(
by='remain_amount', ascending=True, ignore_index=True)
return df_filter
def filter_multiple_factors(df, *, date, multiple_factors_config):
"""多因子筛选
债权+股权
1. 价格
2. 溢价率
3. 剩余市值
4. 正股市值
5. 到期时间
6. 波动率
"""
benchmark_temperature = multiple_factors_config.get(
"benchmark_temperature")
mid_price_bemchmark = multiple_factors_config.get(
"mid_price_bemchmark")
bond_ratio = multiple_factors_config.get("bond_ratio") # 债性系数
stock_ratio = multiple_factors_config.get("stock_ratio")
price_bemchmark = multiple_factors_config.get('price_bemchmark') # 价格基准
premium_bemchmark = multiple_factors_config.get(
'premium_bemchmark') # 溢价率基准
premium_ratio = multiple_factors_config.get(
'premium_ratio')
stock_option_ratio = multiple_factors_config.get(
'stock_option_ratio') # 可转债期权系数 -- 用到期时间衡量, 减分项, 小于一年减分
stock_option_bemchmark_days = multiple_factors_config.get(
"stock_option_bemchmark_days") # 可转债期权基准天数
remain_ratio = multiple_factors_config.get(
'remain_ratio') # 正股剩余市值系数
remain_bemchmark_min = multiple_factors_config.get(
"remain_bemchmark_min") # 剩余市值加分项
remain_bemchmark_max = multiple_factors_config.get(
"remain_bemchmark_max") # 剩余市值减分项
remain_score_min = multiple_factors_config.get(
"remain_score_min") # 剩余市值最低分
stock_pb_ratio = multiple_factors_config.get(
"stock_pb_ratio") # 正股PB系数 减分项, 小于1.5减分
pb_bemchmark = multiple_factors_config.get("pb_bemchmark") # 正股PB基准
pb_score_min = multiple_factors_config.get("pb_score_min") # 正股PB最低分
stock_market_cap_ratio = multiple_factors_config.get(
"stock_market_cap_ratio") # 正股市值系数
stock_market_cap_bemchmark_min = multiple_factors_config.get(
"stock_market_cap_bemchmark_min") # 正股市值加分项
stock_market_cap_bemchmark_max = multiple_factors_config.get(
"stock_market_cap_bemchmark_max") # 正股市值减分项
stock_market_cap_score_min = multiple_factors_config.get(
"stock_market_cap_score_min") # 正股市值最低分
stock_market_cap_score_max = multiple_factors_config.get(
"stock_market_cap_score_max") # 正股市值最高分
stock_stdevry_ratio = multiple_factors_config.get(
'stock_stdevry_ratio') # 正股剩余市值系数
stock_stdevry_bemchmark = multiple_factors_config.get(
"stock_stdevry_bemchmark") # 波动率基准
# 正股波动率最高分
stock_stdevry_score_max = multiple_factors_config.get(
"stock_stdevry_score_max")
stock_stdevry_score_min = multiple_factors_config.get(
"stock_stdevry_score_min") # 正股波动率最低分
max_price = multiple_factors_config.get("max_price") # 最高价
df_filter = df.loc[
(df['date_return_distance'] != '无权')
& (df['is_unlist'] == 'N')
& (~df["cb_name"].str.contains("EB"))
& (df['is_ransom_flag'] == 'False')
& (df['cb_to_pb'] > 0.5) # 排除问题债
& (df['cb_to_pb'] < 15) # 排除问题债
]
weight_score_key = 'weight'
def core_filter(row):
# if weight_score > 1:
# return True
if '天' in row.date_remain_distance and not '年' in row.date_remain_distance:
day_count = float(row.date_remain_distance[0:-1])
return day_count > 90 and row[weight_score_key] > 1
if row.price > max_price:
return False
# if '后可能满足强赎条件' in row.pre_ransom_remark and row.price >= 141:
# return False
return row[weight_score_key] > 1
def calulate_score(row):
# 股权基础分
premium_score = 1 - (row.premium_rate -
premium_bemchmark) / premium_bemchmark
# 债权基础分
price_score = 1 - (row.price - price_bemchmark) / \
price_bemchmark
# mid_price_score =
# 可转债股性市净率评分
pb_score = round(
min(1, max(pb_score_min, 1 - (pb_bemchmark - row.pb) / pb_bemchmark)), 2)
# 可转债股性期权评分
date_format = '%Y-%m-%d' # 日期格式
issue_date = datetime.strptime(row.issue_date, date_format)
now_date = datetime.strptime(date, date_format)
# 计算两个日期之间的天数
days_remain = 365 * 6 - (now_date - issue_date).days
stock_option_score = 1
if days_remain < stock_option_bemchmark_days:
stock_option_score = round(1 -
(stock_option_bemchmark_days - days_remain) /
stock_option_bemchmark_days, 2)
# 可转债股性剩余市值评分
remain_score = 1
if row.remain_amount < remain_bemchmark_min:
remain_score = round(1 -
(row.remain_amount - remain_bemchmark_min) /
remain_bemchmark_min, 2)
elif row.remain_amount > remain_bemchmark_max:
remain_score = max(remain_score_min, round(1 -
(row.remain_amount - remain_bemchmark_max) /
remain_bemchmark_max, 2))
# 可转债股性正股市值评分
stock_market_cap_score = 1
if row.market_cap < stock_market_cap_bemchmark_min:
stock_market_cap_score = round(1 -
(row.market_cap - stock_market_cap_bemchmark_min) /
stock_market_cap_bemchmark_min, 2)
elif row.market_cap > stock_market_cap_bemchmark_max:
stock_market_cap_score = round(1 -
(row.market_cap - stock_market_cap_bemchmark_max) /
stock_market_cap_bemchmark_max, 2)
stock_market_cap_score = min(stock_market_cap_score_max, max(
stock_market_cap_score_min, stock_market_cap_score))
# 正股波动率评分
stock_stdevry_score = round(
1 - (stock_stdevry_bemchmark - row.stock_stdevry) / stock_stdevry_bemchmark, 2)
stock_stdevry_score = min(
stock_stdevry_score_max, max(stock_stdevry_score_min, stock_stdevry_score))
bond_score = round(price_score * bond_ratio, 2)
stock_score = round(stock_ratio *
(
premium_score * premium_ratio +
stock_stdevry_score * stock_stdevry_ratio +
remain_score * remain_ratio +
pb_score * stock_pb_ratio +
stock_option_score * stock_option_ratio +
stock_market_cap_score * stock_market_cap_ratio
), 2)
row['option'] = stock_option_score
row['remain'] = remain_score
row['pb_score'] = pb_score
row['stdevry'] = stock_stdevry_score
row['stock_market_cap'] = stock_market_cap_score
row['bond'] = bond_score
row['stock'] = stock_score
weight_score = bond_score + stock_score
row[weight_score_key] = weight_score
return row
df_filter = df_filter.apply(calulate_score, axis=1)
df_filter = df_filter[df_filter.apply(core_filter, axis=1)]
df_filter = df_filter.sort_values(
by=weight_score_key, ascending=False, ignore_index=True)
return df_filter
def filter_listed_all(df):
df_filter = df.loc[
(~df["cb_name"].str.contains("EB"))
& (df['is_unlist'] == 'N')
]
return df_filter
def filter_listed_all_exclude_new(df):
df_filter = df.loc[
(~df["cb_name"].str.contains("EB"))
& (df['is_unlist'] == 'N')
& (df['last_is_unlist'] == 'N')
]
return df_filter
def filter_downward_revise(df):
df_filter = df.loc[
(df['cb_to_pb'] > 1.2)
& (~df["cb_name"].str.contains("EB"))
& (df['date_convert_distance'] == '已到')
& (~df["repair_flag_remark"].str.contains("暂不行使下修权利"))
& (~df["repair_flag_remark"].str.contains("距离不下修承诺"))
& (df["price"] < 120)
& (df["premium_rate"] > 35)
]
df_filter = df_filter.sort_values(
by='new_style', ascending=True, ignore_index=True)
return df_filter