Skip to content

Commit 5debc2d

Browse files
committed
1)add del_stock_pool/del_hidden_tag api
2)add setting hidden tag in batch_set_stock_tags 3)add QiyeWechatBot
1 parent 13a42a9 commit 5debc2d

File tree

11 files changed

+211
-12
lines changed

11 files changed

+211
-12
lines changed

api-tests/del_stock_pool.http

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DELETE http://127.0.0.1:8090/api/work/del_stock_pool?stock_pool_name=今日弱势
2+
accept: application/json
3+
Content-Type: application/json
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DELETE http://127.0.0.1:8090/api/work/del_hidden_tag?tag=123123
2+
accept: application/json
3+
4+

src/zvt/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"email_password": "",
1010
"wechat_app_id": "",
1111
"wechat_app_secrect": "",
12+
"qiye_wechat_bot_token": "",
1213
"qmt_mini_data_path": "D:\\qmt\\userdata_mini",
1314
"qmt_account_id": "",
1415
"moonshot_api_key": "",

src/zvt/informer/informer.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,29 @@ def _format_price_notification(self, to_user, security_name, current_price, chan
156156
return the_json
157157

158158

159-
if __name__ == "__main__":
160-
email_action = EmailInformer()
161-
email_action.send_message(["5533061@qq.com", "2315983623@qq.com"], "helo", "just a test", sub_size=20)
159+
class QiyeWechatBot(Informer):
160+
def __init__(self, token=None) -> None:
161+
self.token = token
162+
163+
def send_message(self, content):
164+
if not self.token:
165+
if not zvt_config["qiye_wechat_bot_token"]:
166+
logger.warning(f"Please set qiye_wechat_bot_token in ~/zvt-home/config.json")
167+
return
168+
self.token = zvt_config["qiye_wechat_bot_token"]
169+
170+
msg = {
171+
"msgtype": "text",
172+
"text": {"content": content},
173+
}
174+
requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={self.token}", json=msg)
162175

176+
177+
if __name__ == "__main__":
163178
# weixin_action = WechatInformer()
164179
# weixin_action.send_price_notification(to_user='oRvNP0XIb9G3g6a-2fAX9RHX5--Q', security_name='BTC/USDT',
165180
# current_price=1000, change_pct='0.5%')
181+
bot = QiyeWechatBot()
182+
bot.send_message(content="test")
166183
# the __all__ is generated
167184
__all__ = ["Informer", "EmailInformer", "WechatInformer"]

src/zvt/resources/concept_main_tag_mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
"一带一路": "其他",
393393
"碳交易": "其他",
394394
"宠物经济": "其他",
395+
"商业航天": "商业航天",
395396
"航天概念": "商业航天",
396397
"天基互联": "商业航天",
397398
"北斗导航": "商业航天",

src/zvt/rest/work.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def create_stock_pools(create_stock_pools_model: CreateStockPoolsModel):
6565
return tag_service.build_stock_pool(create_stock_pools_model, current_date())
6666

6767

68+
@work_router.delete("/del_stock_pool", response_model=str)
69+
def del_stock_pool(stock_pool_name: str):
70+
return tag_service.del_stock_pool(stock_pool_name=stock_pool_name)
71+
72+
6873
@work_router.get("/get_stock_pools", response_model=Optional[StockPoolsModel])
6974
def get_stock_pools(stock_pool_name: str):
7075
with contract_api.DBSession(provider="zvt", data_schema=StockPools)() as session:
@@ -261,3 +266,8 @@ def build_main_tag_sub_tag_relation(relation: MainTagSubTagRelation):
261266
@work_router.post("/change_main_tag", response_model=List[StockTagsModel])
262267
def change_main_tag(change_main_tag_model: ChangeMainTagModel):
263268
return tag_service.change_main_tag(change_main_tag_model=change_main_tag_model)
269+
270+
271+
@work_router.delete("/del_hidden_tag", response_model=List[StockTagsModel])
272+
def del_hidden_tag(tag: str):
273+
return tag_service.del_hidden_tag(tag=tag)

src/zvt/tag/tag_models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ class TagParameter(CustomModel):
9090
main_tag_reason: Optional[str] = Field(default=None)
9191
sub_tag: Optional[str] = Field(default=None)
9292
sub_tag_reason: Optional[str] = Field(default=None)
93+
hidden_tag: Optional[str] = Field(default=None)
94+
hidden_tag_reason: Optional[str] = Field(default=None)
9395

9496

9597
class StockTagOptions(CustomModel):

src/zvt/tag/tag_service.py

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
logger = logging.getLogger(__name__)
4848

4949

50-
def stock_tags_need_update(stock_tags: StockTags, set_stock_tags_model: SetStockTagsModel):
50+
def _stock_tags_need_update(stock_tags: StockTags, set_stock_tags_model: SetStockTagsModel):
5151
if (
5252
stock_tags.main_tag != set_stock_tags_model.main_tag
5353
or stock_tags.main_tag_reason != set_stock_tags_model.main_tag_reason
@@ -59,7 +59,7 @@ def stock_tags_need_update(stock_tags: StockTags, set_stock_tags_model: SetStock
5959
return False
6060

6161

62-
def get_stock_tag_options(entity_id):
62+
def get_stock_tag_options(entity_id: str) -> StockTagOptions:
6363
with contract_api.DBSession(provider="zvt", data_schema=StockTags)() as session:
6464
datas: List[StockTags] = StockTags.query_data(
6565
entity_id=entity_id, order=StockTags.timestamp.desc(), limit=1, return_type="domain", session=session
@@ -175,7 +175,7 @@ def build_stock_tags(
175175
current_stock_tags: StockTags = datas[0]
176176

177177
# nothing change
178-
if not stock_tags_need_update(current_stock_tags, set_stock_tags_model):
178+
if not _stock_tags_need_update(current_stock_tags, set_stock_tags_model):
179179
logger.info(f"Not change stock_tags for {set_stock_tags_model.entity_id}")
180180
return current_stock_tags
181181

@@ -224,6 +224,9 @@ def build_stock_tags(
224224

225225

226226
def build_tag_parameter(tag_type: TagType, tag, tag_reason, stock_tag: StockTags):
227+
hidden_tag = None
228+
hidden_tag_reason = None
229+
227230
if tag_type == TagType.main_tag:
228231
main_tag = tag
229232
if main_tag in stock_tag.main_tags:
@@ -240,11 +243,29 @@ def build_tag_parameter(tag_type: TagType, tag, tag_reason, stock_tag: StockTags
240243
sub_tag_reason = tag_reason
241244
main_tag = stock_tag.main_tag
242245
main_tag_reason = stock_tag.main_tag_reason
246+
elif tag_type == TagType.hidden_tag:
247+
hidden_tag = tag
248+
if stock_tag.hidden_tags and (hidden_tag in stock_tag.hidden_tags):
249+
hidden_tag_reason = stock_tag.hidden_tags.get(hidden_tag, tag_reason)
250+
else:
251+
hidden_tag_reason = tag_reason
252+
253+
sub_tag = stock_tag.sub_tag
254+
sub_tag_reason = stock_tag.sub_tag_reason
255+
256+
main_tag = stock_tag.main_tag
257+
main_tag_reason = stock_tag.main_tag_reason
258+
243259
else:
244260
assert False
245261

246262
return TagParameter(
247-
main_tag=main_tag, main_tag_reason=main_tag_reason, sub_tag=sub_tag, sub_tag_reason=sub_tag_reason
263+
main_tag=main_tag,
264+
main_tag_reason=main_tag_reason,
265+
sub_tag=sub_tag,
266+
sub_tag_reason=sub_tag_reason,
267+
hidden_tag=hidden_tag,
268+
hidden_tag_reason=hidden_tag_reason,
248269
)
249270

250271

@@ -274,6 +295,15 @@ def batch_set_stock_tags(batch_set_stock_tags_model: BatchSetStockTagsModel):
274295
session=session,
275296
return_type="domain",
276297
)
298+
elif tag_type == TagType.hidden_tag:
299+
hidden_tag = batch_set_stock_tags_model.tag
300+
stock_tags: List[StockTags] = StockTags.query_data(
301+
entity_ids=batch_set_stock_tags_model.entity_ids,
302+
# 需要sqlite3版本>=3.37.0
303+
filters=[func.json_extract(StockTags.active_hidden_tags, f'$."{hidden_tag}"') == None],
304+
session=session,
305+
return_type="domain",
306+
)
277307

278308
for stock_tag in stock_tags:
279309
tag_parameter: TagParameter = build_tag_parameter(
@@ -282,13 +312,18 @@ def batch_set_stock_tags(batch_set_stock_tags_model: BatchSetStockTagsModel):
282312
tag_reason=batch_set_stock_tags_model.tag_reason,
283313
stock_tag=stock_tag,
284314
)
315+
if tag_type == TagType.hidden_tag:
316+
active_hidden_tags = {batch_set_stock_tags_model.tag: batch_set_stock_tags_model.tag_reason}
317+
else:
318+
active_hidden_tags = stock_tag.active_hidden_tags
319+
285320
set_stock_tags_model = SetStockTagsModel(
286321
entity_id=stock_tag.entity_id,
287322
main_tag=tag_parameter.main_tag,
288323
main_tag_reason=tag_parameter.main_tag_reason,
289324
sub_tag=tag_parameter.sub_tag,
290325
sub_tag_reason=tag_parameter.sub_tag_reason,
291-
active_hidden_tags=stock_tag.active_hidden_tags,
326+
active_hidden_tags=active_hidden_tags,
292327
)
293328

294329
build_stock_tags(
@@ -521,6 +556,23 @@ def build_stock_pool(create_stock_pools_model: CreateStockPoolsModel, target_dat
521556
return stock_pool
522557

523558

559+
def del_stock_pool(stock_pool_name: str):
560+
with contract_api.DBSession(provider="zvt", data_schema=StockPoolInfo)() as session:
561+
stock_pool_info = StockPoolInfo.query_data(
562+
session=session,
563+
filters=[StockPoolInfo.stock_pool_name == stock_pool_name],
564+
return_type="domain",
565+
)
566+
567+
contract_api.del_data(data_schema=StockPools, filters=[StockPools.stock_pool_name == stock_pool_name])
568+
569+
if stock_pool_info:
570+
session.delete(stock_pool_info[0])
571+
session.commit()
572+
return "success"
573+
return "not found"
574+
575+
524576
def query_stock_tag_stats(query_stock_tag_stats_model: QueryStockTagStatsModel):
525577
with contract_api.DBSession(provider="zvt", data_schema=TagStats)() as session:
526578
datas = TagStats.query_data(
@@ -701,6 +753,43 @@ def activate_sub_tags(activate_sub_tags_model: ActivateSubTagsModel):
701753
return result
702754

703755

756+
def remove_hidden_tag(hidden_tag: str):
757+
with contract_api.DBSession(provider="zvt", data_schema=StockTags)() as session:
758+
stock_tags = StockTags.query_data(
759+
session=session,
760+
# 需要sqlite3版本>=3.37.0
761+
filters=[func.json_extract(StockTags.hidden_tags, f'$."{hidden_tag}"') != None],
762+
return_type="domain",
763+
)
764+
if not stock_tags:
765+
logger.info(f"all stocks with hidden_tag: {hidden_tag} has been removed")
766+
return []
767+
for stock_tag in stock_tags:
768+
hidden_tags = dict(stock_tag.hidden_tags)
769+
hidden_tags.pop(hidden_tag)
770+
stock_tag.hidden_tags = hidden_tags
771+
session.commit()
772+
session.refresh(stock_tag)
773+
return stock_tags
774+
775+
776+
def del_hidden_tag(tag: str):
777+
with contract_api.DBSession(provider="zvt", data_schema=HiddenTagInfo)() as session:
778+
hidden_tag_info = HiddenTagInfo.query_data(
779+
session=session,
780+
filters=[HiddenTagInfo.tag == tag],
781+
return_type="domain",
782+
)
783+
if not hidden_tag_info:
784+
logger.info(f"hidden_tag: {tag} has been removed")
785+
return []
786+
787+
result = remove_hidden_tag(hidden_tag=tag)
788+
session.delete(hidden_tag_info[0])
789+
session.commit()
790+
return result
791+
792+
704793
def _create_main_tag_if_not_existed(main_tag, main_tag_reason):
705794
main_tag_info = CreateTagInfoModel(tag=main_tag, tag_reason=main_tag_reason)
706795
if not is_tag_info_existed(tag_info=main_tag_info, tag_type=TagType.main_tag):
@@ -818,13 +907,13 @@ def change_main_tag(change_main_tag_model: ChangeMainTagModel):
818907

819908

820909
if __name__ == "__main__":
821-
activate_industry_list(industry_list=["半导体"])
910+
print(del_hidden_tag(tag="妖"))
911+
# activate_industry_list(industry_list=["半导体"])
822912
# activate_sub_tags(ActivateSubTagsModel(sub_tags=["航天概念", "天基互联", "北斗导航", "通用航空"]))
823913

824914

825915
# the __all__ is generated
826916
__all__ = [
827-
"stock_tags_need_update",
828917
"get_stock_tag_options",
829918
"build_stock_tags",
830919
"build_tag_parameter",

src/zvt/trading/trading_schemas.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
from sqlalchemy import Column, Float, DateTime
2+
from sqlalchemy import Column, Float, DateTime, Integer
33
from sqlalchemy import String, JSON
44
from sqlalchemy.orm import declarative_base
55

@@ -9,6 +9,18 @@
99
TradingBase = declarative_base()
1010

1111

12+
class TagQuoteStats(Mixin, TradingBase):
13+
__tablename__ = "tag_quote_stats"
14+
stock_pool_name = Column(String)
15+
main_tag = Column(String)
16+
limit_up_count = Column(Integer)
17+
limit_down_count = Column(Integer)
18+
up_count = Column(Integer)
19+
down_count = Column(Integer)
20+
change_pct = Column(Float)
21+
turnover = Column(Float)
22+
23+
1224
class TradingPlan(TradingBase, Mixin):
1325
__tablename__ = "trading_plan"
1426
stock_id = Column(String)

src/zvt/trading/trading_service.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
KdataRequestModel,
2121
TSRequestModel,
2222
)
23-
from zvt.trading.trading_schemas import TradingPlan, QueryStockQuoteSetting
23+
from zvt.trading.trading_schemas import TradingPlan, QueryStockQuoteSetting, TagQuoteStats
2424
from zvt.utils.pd_utils import pd_is_not_null
2525
from zvt.utils.time_utils import (
2626
to_time_str,
@@ -229,6 +229,62 @@ def cal_quote_stats(quote_df):
229229
return df.to_dict(orient="records")[0]
230230

231231

232+
def cal_tag_quote_stats(stock_pool_name):
233+
stock_pools: List[StockPools] = StockPools.query_data(
234+
filters=[StockPools.stock_pool_name == stock_pool_name],
235+
order=StockPools.timestamp.desc(),
236+
limit=1,
237+
return_type="domain",
238+
)
239+
if stock_pools:
240+
entity_ids = stock_pools[0].entity_ids
241+
else:
242+
entity_ids = None
243+
244+
tag_df = StockTags.query_data(
245+
entity_ids=entity_ids,
246+
filters=[StockTags.main_tag.isnot(None)],
247+
columns=[StockTags.entity_id, StockTags.main_tag],
248+
return_type="df",
249+
index="entity_id",
250+
)
251+
252+
entity_ids = tag_df["entity_id"].tolist()
253+
254+
quote_df = StockQuote.query_data(entity_ids=entity_ids, return_type="df", index="entity_id")
255+
timestamp = quote_df["timestamp"].tolist()[0]
256+
257+
df = pd.concat([tag_df, quote_df], axis=1)
258+
grouped_df = (
259+
df.groupby("main_tag")
260+
.agg(
261+
up_count=("change_pct", lambda x: (x > 0).sum()),
262+
down_count=("change_pct", lambda x: (x <= 0).sum()),
263+
turnover=("turnover", "sum"),
264+
change_pct=("change_pct", "mean"),
265+
limit_up_count=("is_limit_up", "sum"),
266+
limit_down_count=("is_limit_down", lambda x: (x == True).sum()),
267+
total_count=("main_tag", "size"), # 添加计数,计算每个分组的总行数
268+
)
269+
.reset_index(drop=False)
270+
)
271+
grouped_df["stock_pool_name"] = stock_pool_name
272+
273+
grouped_df["entity_id"] = grouped_df[["stock_pool_name", "main_tag"]].apply(
274+
lambda se: "{}_{}".format(se["stock_pool_name"], se["main_tag"]), axis=1
275+
)
276+
grouped_df["timestamp"] = timestamp
277+
grouped_df["id"] = grouped_df[["entity_id", "timestamp"]].apply(
278+
lambda se: "{}_{}".format(se["entity_id"], to_time_str(se["timestamp"])), axis=1
279+
)
280+
281+
print(grouped_df)
282+
283+
contract_api.df_to_db(
284+
df=grouped_df, data_schema=TagQuoteStats, provider="zvt", force_update=True, drop_duplicates=False
285+
)
286+
287+
232288
def query_tag_quotes(query_tag_quote_model: QueryTagQuoteModel):
233289
stock_pools: List[StockPools] = StockPools.query_data(
234290
filters=[StockPools.stock_pool_name == query_tag_quote_model.stock_pool_name],

0 commit comments

Comments
 (0)