From 950d95a41a73aeda337d31231e752b592f855b2f Mon Sep 17 00:00:00 2001 From: foolcage <5533061@qq.com> Date: Thu, 16 May 2024 22:12:44 +0800 Subject: [PATCH] qmt trading --- api-tests/get_stock_pools.http | 2 +- api-tests/tag/build_stock_tags.http | 612 ------------------ api-tests/tag/query_simple_stock_tags.http | 6 +- api-tests/tag/query_stock_tags.http | 4 +- api-tests/tag/set_stock_tags.http | 20 +- api-tests/trading/query_stock_quotes.http | 9 + examples/utils.py | 34 +- {scripts => examples}/z.sh | 0 scripts/qmt_runner.py | 22 - scripts/report_stock.py | 13 - scripts/report_stockhk.py | 10 - src/fill_project.py | 7 + src/zvt/__init__.py | 13 +- src/zvt/broker/qmt/context.py | 26 + src/zvt/broker/qmt/errors.py | 15 + src/zvt/broker/qmt/qmt_account.py | 104 ++- src/zvt/broker/qmt/qmt_api.py | 267 -------- src/zvt/broker/qmt/qmt_quote.py | 255 ++++++++ src/zvt/common/query_models.py | 5 + src/zvt/common/trading_models.py | 26 + src/zvt/config.json | 4 +- src/zvt/contract/api.py | 58 +- src/zvt/contract/model.py | 6 +- src/zvt/domain/meta/stock_meta.py | 10 - src/zvt/domain/misc/__init__.py | 6 - src/zvt/domain/quotes/stock/stock_quote.py | 12 +- src/zvt/recorders/em/em_api.py | 131 ++-- src/zvt/recorders/em/misc/__init__.py | 6 - .../qmt/quotes/qmt_kdata_recorder.py | 4 +- src/zvt/rest/trading.py | 27 +- src/zvt/tag/tag_service.py | 175 ++--- src/zvt/trading/trading_models.py | 62 +- src/zvt/trading/trading_service.py | 74 ++- src/zvt/xtquant.py | 171 ----- src/zvt_vip/__init__.py | 2 + src/zvt_vip/dataset/__init__.py | 13 + .../misc => zvt_vip/dataset}/stock_events.py | 0 src/zvt_vip/recorders/__init__.py | 19 + src/zvt_vip/recorders/em_api.py | 58 ++ .../recorders}/em_stock_events_recorder.py | 11 +- {scripts => src/zvt_vip/scripts}/__init__.py | 0 src/zvt_vip/scripts/kdata_runner.py | 145 +++++ .../zvt_vip/scripts}/prepare_recent_data.py | 11 +- .../zvt_vip/scripts}/report_stock_by_tag.py | 3 +- src/zvt_vip/tag/__init__.py | 13 + src/zvt_vip/tag/tag_service.py | 191 ++++++ 46 files changed, 1252 insertions(+), 1410 deletions(-) create mode 100644 api-tests/trading/query_stock_quotes.http rename {scripts => examples}/z.sh (100%) delete mode 100644 scripts/qmt_runner.py delete mode 100644 scripts/report_stock.py delete mode 100644 scripts/report_stockhk.py create mode 100644 src/fill_project.py create mode 100644 src/zvt/broker/qmt/context.py create mode 100644 src/zvt/broker/qmt/errors.py delete mode 100644 src/zvt/broker/qmt/qmt_api.py create mode 100644 src/zvt/broker/qmt/qmt_quote.py create mode 100644 src/zvt/common/trading_models.py delete mode 100644 src/zvt/xtquant.py create mode 100644 src/zvt_vip/__init__.py create mode 100644 src/zvt_vip/dataset/__init__.py rename src/{zvt/domain/misc => zvt_vip/dataset}/stock_events.py (100%) create mode 100644 src/zvt_vip/recorders/__init__.py create mode 100644 src/zvt_vip/recorders/em_api.py rename src/{zvt/recorders/em/misc => zvt_vip/recorders}/em_stock_events_recorder.py (89%) rename {scripts => src/zvt_vip/scripts}/__init__.py (100%) create mode 100644 src/zvt_vip/scripts/kdata_runner.py rename {scripts => src/zvt_vip/scripts}/prepare_recent_data.py (79%) rename {scripts => src/zvt_vip/scripts}/report_stock_by_tag.py (98%) create mode 100644 src/zvt_vip/tag/__init__.py create mode 100644 src/zvt_vip/tag/tag_service.py diff --git a/api-tests/get_stock_pools.http b/api-tests/get_stock_pools.http index 7e10a0d3..0c6f1c15 100644 --- a/api-tests/get_stock_pools.http +++ b/api-tests/get_stock_pools.http @@ -1,4 +1,4 @@ -GET http://127.0.0.1:8090/api/work/get_stock_pools?stock_pool_name=vol_up +GET http://127.0.0.1:8090/api/work/get_stock_pools?stock_pool_name=main_line accept: application/json diff --git a/api-tests/tag/build_stock_tags.http b/api-tests/tag/build_stock_tags.http index 67baf4a9..ba957231 100644 --- a/api-tests/tag/build_stock_tags.http +++ b/api-tests/tag/build_stock_tags.http @@ -11,617 +11,5 @@ Content-Type: application/json "sub_tag": "低空经济", "sub_tag_reason": "2023年12月27日回复称,公司钻石eDA40纯电动飞机已成功首飞;eVTOL项目已联动海外钻石技术开发团队,在绿色、智能、垂直起降等方面的设计体现未来领域应用场景。", "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_301091", - "name": "深城交", - "main_tag": "低空经济", - "main_tag_reason": "2024年3月8日回复称,目前公司已与另外一家深圳研究咨询机构组成的联合体正式承接了深圳低空智能融合基础设施建设项目一期项目,该项目主要围绕深圳市低空经济发展,建设可覆盖全市范围的智能融合系统的软件平台(包括低空操作管理系统和低空管理服务系统),建设配套的管服中心、数据中心及无人机测试场,接入典型的城市场景,并进行软件平台的验证。", - "sub_tag": "低空经济", - "sub_tag_reason": "2024年3月8日回复称,目前公司已与另外一家深圳研究咨询机构组成的联合体正式承接了深圳低空智能融合基础设施建设项目一期项目,该项目主要围绕深圳市低空经济发展,建设可覆盖全市范围的智能融合系统的软件平台(包括低空操作管理系统和低空管理服务系统),建设配套的管服中心、数据中心及无人机测试场,接入典型的城市场景,并进行软件平台的验证。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002716", - "name": "湖南白银", - "main_tag": "资源", - "main_tag_reason": "来自行业:贵金属", - "sub_tag": "黄金概念", - "sub_tag_reason": "公司是一家从事“从富含银的铅精矿及铅冶炼废渣废液中综合回收白银及铅、金、铋、锑、锌、铜、铟等多种有色金属”的高新技术企业,位于“中国银都——郴州”,是我国白银生产出口的重要基地之一,拥有全国领先的白银冶炼和深加工技术,白银年产量居全国同类企业前列。目前,公司的白银生产技术、工艺水平、产品质量、资源综合利用率在同行业均处于领先地位。白银回收率可达99.5%,资源综合利用率达95%。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002805", - "name": "丰元股份", - "main_tag": "新能源", - "main_tag_reason": "2023年2月13日回复称公司在固态电池正极材料方面有相应研发布局,同时公司参股了相关公司。", - "sub_tag": "固态电池", - "sub_tag_reason": "2023年2月13日回复称公司在固态电池正极材料方面有相应研发布局,同时公司参股了相关公司。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000888", - "name": "峨眉山A", - "main_tag": "大消费", - "main_tag_reason": "来自行业:旅游酒店", - "sub_tag": "在线旅游", - "sub_tag_reason": "公司主要从事峨眉山风景区游山门票、客运索道、宾馆酒店服务以及其他相关旅游服务的经营。本报告期,游山门票收入占营业收入的43%;客运索道收入占营业收入29.83%;宾馆酒店收入占营业收入16.65%。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_601212", - "name": "白银有色", - "main_tag": "资源", - "main_tag_reason": "来自行业:有色金属", - "sub_tag": "黄金概念", - "sub_tag_reason": "公司的主要业务为铜,铅,锌,金,银等多种有色金属的采选,冶炼,加工及贸易,业务覆盖有色金属全产业链,是具有深厚行业积淀并初步形成国际布局的行业领先的大型有色金属企业。公司拥有的矿产保有资源储量为:铜43.34万吨,铅127.93万吨,锌590.80万吨,金16,304.08千克,银1667.19吨,钼2.86万吨。公司在秘鲁设立控股子公司首信秘鲁公司,未来可实现铜,铁,金,银等多金属选矿680万吨/年的生产能力。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300139", - "name": "晓程科技", - "main_tag": "半导体", - "main_tag_reason": "来自行业:半导体", - "sub_tag": "黄金概念", - "sub_tag_reason": "2019年年报显示公司主营产品包括黄金。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_601179", - "name": "中国西电", - "main_tag": "电力", - "main_tag_reason": "来自行业:电网设备", - "sub_tag": "特高压", - "sub_tag_reason": "中国西电是目前我国电压等级最高、产品品种最多、工程成套能力最强的企业。公司主导产品是110kV及以上电压等级的高压开关、变压器、电抗器、电力电容器、互感器、直流输电换流阀等。产品已在西北750kV示范工程成功运行,国内首台800kV双断口罐式断路器在银川东变电站已经投入运行", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603993", - "name": "洛阳钼业", - "main_tag": "资源", - "main_tag_reason": "来自行业:小金属", - "sub_tag": "黄金概念", - "sub_tag_reason": "铜矿伴有金", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_600988", - "name": "赤峰黄金", - "main_tag": "资源", - "main_tag_reason": "来自行业:贵金属", - "sub_tag": "黄金概念", - "sub_tag_reason": "公司以1元将全部资产及负债(评估值-842.18万元)出售给威远集团,同时拟8.68元/股向吉隆矿业全体股东(8位自然人)发行1.84亿股购买吉隆矿业100%股权(评估值15.94亿元,增值率1085.37%),前述两项交易互为生效条件。本次交易完成后,公司主营业务将变更为黄金采选及销售,公司的控股股东和实际控制人都将由吴培青变更为赵美光。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002378", - "name": "章源钨业", - "main_tag": "资源", - "main_tag_reason": "来自行业:小金属", - "sub_tag": "小金属概念", - "sub_tag_reason": "公司是国内具备钨行业完整产业链的少数厂商之一,钨粉及碳化钨粉前二名供应商、硬质合金前四名供应商,建立了从钨上游采矿、选矿,中游冶炼至下游精深加工的完整一体化生产体系。公司拥有淘锡坑钨矿、新安子钨锡矿、大余石雷钨矿、天井窝钨矿四个采矿权矿山。采矿区总面积约39.2654平方公里,其中保有钨储量9.60万吨,占全国总量的5.33%。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_600667", - "name": "太极实业", - "main_tag": "半导体", - "main_tag_reason": "半导体", - "sub_tag": "存储芯片", - "sub_tag_reason": "公司半导体业务是为DRAM和NAND Flash等集成电路产品提供封装、封装测试、模组装配和模组测试等后工序服务。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_601028", - "name": "玉龙股份", - "main_tag": "大消费", - "main_tag_reason": "来自行业:贸易行业", - "sub_tag": "黄金概念", - "sub_tag_reason": "2023年2月28日回复称公司围绕黄金+新能源新材料矿产“双轮驱动”战略,已在金、矾、锂、石墨领域有所涉足。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300252", - "name": "金信诺", - "main_tag": "AI", - "main_tag_reason": "来自行业:通信设备", - "sub_tag": "铜缆高速连接", - "sub_tag_reason": "金信诺专业从事基于“深度覆盖”和“可靠连接”的信号互联产品的研发、生产和销售,主要有线缆/连接器/组件类、PCB(印刷线路板)类和系统/终端类的三大类产品业务,服务全球多行业、多领域的顶尖客户,并为客户提供“designin”的定制研发产品和方案。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002632", - "name": "道明光学", - "main_tag": "消费电子", - "main_tag_reason": "AI手机材料", - "sub_tag": "AI手机", - "sub_tag_reason": "2024年3月4日回复称,公司石墨烯散热膜已应用于OPPO Find N3 Flip折叠屏手机及努比亚红魔8S Pro、8/8pro/8pro+、红魔9Pro、Z50型号手机及其他品牌产品。同时,oppo官网显示,OPPO Find N3 Flip属于AI手机,并标志着oppo正式进入AI手机时代。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300057", - "name": "万顺新材", - "main_tag": "资源", - "main_tag_reason": "来自行业:有色金属", - "sub_tag": "复合集流体", - "sub_tag_reason": "2022年半年报显示公司一贯重视技术研发创新,参加“高附着性铝层电子复合铝膜的研究开发”项目。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300919", - "name": "中伟股份", - "main_tag": "新能源", - "main_tag_reason": "来自行业:电池", - "sub_tag": "动力电池回收", - "sub_tag_reason": "2021年9月11日回复称公司现阶段的电池回收业务不仅通过线下渠道收购少量新能源汽车电池,兼回收少量冶炼企业的副产品。回收的镍钴锰金属已应用于公司产品的生产。公司正根据锂电池退役时间段布局回收渠道,其中包括电池厂、整车厂及部分线下渠道。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300576", - "name": "容大感光", - "main_tag": "半导体", - "main_tag_reason": "来自行业:电子化学品", - "sub_tag": "光刻胶", - "sub_tag_reason": "公司的光刻胶产品主要包括紫外线正胶、紫外线负胶两大类产品以及稀释剂、显影液、剥离液等配套化学品,主要应用于平板显示、发光二极管及集成电路等领域。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300637", - "name": "扬帆新材", - "main_tag": "半导体", - "main_tag_reason": "来自行业:电子化学品", - "sub_tag": "光刻胶", - "sub_tag_reason": "公司持续开发出新的光引发剂产品,满足下游食品包装印刷、 3D 打印、光刻胶等应用领域的客户需求,目前已进入测评阶段,未来有望成为公司业绩的新增长点。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_605218", - "name": "伟时电子", - "main_tag": "消费电子", - "main_tag_reason": "来自行业:光学光电子", - "sub_tag": "虚拟现实", - "sub_tag_reason": "2023年6月6日回复称公司为VR提供背光显示模组等相关产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_600482", - "name": "中国动力", - "main_tag": "军工", - "main_tag_reason": "来自行业:船舶制造", - "sub_tag": "军工", - "sub_tag_reason": "2019年12月24日回复称航母动力系统为公司下属子公司广瀚动力提供,包括锅炉、汽轮机、传动系统。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002850", - "name": "科达利", - "main_tag": "新能源", - "main_tag_reason": "来自行业:电池", - "sub_tag": "锂电池", - "sub_tag_reason": "公司主营业务为锂电池精密结构件和汽车结构件研发及制造。公司主要产品为锂电池精密结构件(包括动力及储能锂电池精密结构件、便携式锂电池精密结构件)、汽车结构件。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300505", - "name": "川金诺", - "main_tag": "材料", - "main_tag_reason": "来自行业:化肥行业", - "sub_tag": "磷化工", - "sub_tag_reason": "2020年年报显示公司旗下有全资子公司云南庆磷磷肥有限公司。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002215", - "name": "诺 普 信", - "main_tag": "农业", - "main_tag_reason": "来自行业:农药兽药", - "sub_tag": "农业种植", - "sub_tag_reason": "公司参股新三板美奥种业,参股比例20%,该公司主营瓜菜种子的研发、生产和销售业务。。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002737", - "name": "葵花药业", - "main_tag": "医药", - "main_tag_reason": "来自行业:中药", - "sub_tag": "中药概念", - "sub_tag_reason": "公司主营业务为各类中成药、化学药品(包括生物制药)的研发、生产和销售。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002298", - "name": "中电兴发", - "main_tag": "AI", - "main_tag_reason": "来自行业:软件开发", - "sub_tag": "存储芯片", - "sub_tag_reason": "中电兴发(002298.SZ)3月15日在投资者互动平台表示,公司的存储相关产品和技术为公司自主研发。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000903", - "name": "云内动力", - "main_tag": "汽车", - "main_tag_reason": "来自行业:汽车零部件", - "sub_tag": "氢能源", - "sub_tag_reason": "2022年1月4日公告显示公司与众宇动力、煦和商贸共同组建了云南合原新能源动力科技有限公司,开展氢燃料电池系统的研发、生产和销售工作。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300620", - "name": "光库科技", - "main_tag": "AI", - "main_tag_reason": "来自行业:通信设备", - "sub_tag": "CPO概念", - "sub_tag_reason": "2023年04月18日回复称公司产品主要应用于超高速干线光通信网、超高速数据中心、人工智能、超算中心、海底光通信网、城域核心网、微波光子、测试及科研等领域。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002432", - "name": "九安医疗", - "main_tag": "医疗器械", - "main_tag_reason": "来自行业:医疗器械", - "sub_tag": "Kimi概念", - "sub_tag_reason": "公司是砺思资本的有限合伙人,砺思资本参股了Kimi的母公司月之暗面。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002400", - "name": "省广集团", - "main_tag": "文化传媒", - "main_tag_reason": "来自行业:文化传媒", - "sub_tag": "字节概念", - "sub_tag_reason": "2020年5月15日回复称公司成为TikTokAds出海核心代理,有利于推动公司出海业务的发展。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_600522", - "name": "中天科技", - "main_tag": "新能源", - "main_tag_reason": "光伏,储能", - "sub_tag": "储能", - "sub_tag_reason": "2020年年报显示公司在储能系统方面以大型储能系统技术为核心竞争力,发力电网侧,用户侧及电源侧储能应用,并承接了多项重大工程项目,累计达500余MWh,将绿色能源系统解决方案与制造业服务化深度融合。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300777", - "name": "中简科技", - "main_tag": "材料", - "main_tag_reason": "来自行业:化纤行业", - "sub_tag": "碳纤维", - "sub_tag_reason": "高性能碳纤维、织物、复合材料及相关产品的开发、制造、销售。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603688", - "name": "石英股份", - "main_tag": "半导体", - "main_tag_reason": "来自行业:非金属材料", - "sub_tag": "国产芯片", - "sub_tag_reason": "2019年10月24日公告显示公司对上海强华实业股份有限公司进行投资,上海强华开发生产半导体集成电路6英寸~8英寸芯片用石英玻璃制品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_600895", - "name": "张江高科", - "main_tag": "AI", - "main_tag_reason": "光刻胶", - "sub_tag": "光刻胶", - "sub_tag_reason": "2020年3月回复称上海张江浩成创业投资有限公司2016年投资了上海微电子装备有限公司22,345万元人民币,投资比例为10.779%,后者有光刻机产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002792", - "name": "通宇通讯", - "main_tag": "AI", - "main_tag_reason": "来自行业:通信设备", - "sub_tag": "6G概念", - "sub_tag_reason": "2023年3月9日回复称公司针对6G技术的研究处于规划预研阶段,有一定技术储备和积累。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300429", - "name": "强力新材", - "main_tag": "半导体", - "main_tag_reason": "来自行业:电子化学品", - "sub_tag": "光刻胶", - "sub_tag_reason": "2018年公司主营业务产品仍以光刻胶专用化学品为主,分为光刻胶用光引发剂(包括光增感剂、光致产酸剂等)和光刻胶树脂两大系列。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002156", - "name": "通富微电", - "main_tag": "半导体", - "main_tag_reason": "来自行业:半导体", - "sub_tag": "Chiplet概念", - "sub_tag_reason": "2022年8月1日回复称公司在Chiplet、WLP、SiP、Fanout、2.5D、3D堆叠等方面均有布局和储备。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002340", - "name": "格林美", - "main_tag": "资源", - "main_tag_reason": "来自行业:能源金属", - "sub_tag": "动力电池回收", - "sub_tag_reason": "2021年12月4日回复称格林美就是具备“回收体系、梯级利用、资源化利用”等三个要素的全球先进企业,能够对动力电池实施全国范围的有效回收、梯级利用与完整资源化利用,构建了“动力电池回收—梯级利用—原料再制造—材料再制造—动力电池包再造”的新能源全生命周期价值链。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_688027", - "name": "国盾量子", - "main_tag": "AI", - "main_tag_reason": "来自行业:通信设备", - "sub_tag": "量子通信", - "sub_tag_reason": "公司主要从事量子通信产品的研发、生产、销售及技术服务,为各类光纤量子保密通信网络以及星地一体广域量子保密通信地面站的建设系统地提供软硬件产品,为政务、金融、电力、国防等行业和领域提供组网及量子安全应用解决方案。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603960", - "name": "克来机电", - "main_tag": "智能机器", - "main_tag_reason": "来自行业:专用设备", - "sub_tag": "机器人概念", - "sub_tag_reason": "2023年7月11日回复称公司从事工业机器人相关业务多年。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300394", - "name": "天孚通信", - "main_tag": "AI", - "main_tag_reason": "来自行业:通信设备", - "sub_tag": "CPO概念", - "sub_tag_reason": "2021年年报显示公司有激光芯片集成高速光引擎研发项目开发适用于CPO方案使用的高速光引擎,为CPO技术提供一站式整合解决方案。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603900", - "name": "莱绅通灵", - "main_tag": "资源", - "main_tag_reason": "来自行业:珠宝首饰", - "sub_tag": "黄金概念", - "sub_tag_reason": "2019年年报显示公司所处行业为珠宝首饰零售业,主要产品包括钻石、宝石、翡翠、黄金、铂金等材料制成的珠宝首饰。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000701", - "name": "厦门信达", - "main_tag": "大消费", - "main_tag_reason": "来自行业:商业百货", - "sub_tag": "小米汽车", - "sub_tag_reason": "2024年3月25日回复称,公司积极探索与小米汽车的合作机会,目前公司已取得小米汽车昆明钣喷中心授权,相关门店正在建设中。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_688499", - "name": "利元亨", - "main_tag": "新能源", - "main_tag_reason": "来自行业:专用设备", - "sub_tag": "固态电池", - "sub_tag_reason": "2022年2月23日公司互动:在新型储能领域,公司目前已有液流电池储能和锂电池储能相关设备的研发、订单和专利储备。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300758", - "name": "七彩化学", - "main_tag": "材料", - "main_tag_reason": "来自行业:化学制品", - "sub_tag": "光刻胶", - "sub_tag_reason": "2021年7月15日回复称公司控股子公司绍兴上虞新利化工有限公司“2000吨/年光敏性中间体及600吨/年高性能光刻胶系列产品技改项目”目前正在安装设备阶段。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300254", - "name": "仟源医药", - "main_tag": "医药", - "main_tag_reason": "来自行业:化学制药", - "sub_tag": "中药概念", - "sub_tag_reason": "2020年9月23日回复称公司下属子公司四川仟源中药饮片有限公司主要产品有建曲、胆南星、蟾酥粉等中药饮片产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300254", - "name": "仟源医药", - "main_tag": "医药", - "main_tag_reason": "来自行业:化学制药", - "sub_tag": "中药概念", - "sub_tag_reason": "2020年9月23日回复称公司下属子公司四川仟源中药饮片有限公司主要产品有建曲、胆南星、蟾酥粉等中药饮片产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_301025", - "name": "读客文化", - "main_tag": "文化传媒", - "main_tag_reason": "来自行业:文化传媒", - "sub_tag": "AI语料", - "sub_tag_reason": "公司业务包括纸质图书业务、数字内容业务、版权运营业务、新媒体业务。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603810", - "name": "丰山集团", - "main_tag": "新能源", - "main_tag_reason": "2022年8月12日公告显示公司拟与南通全诺合资组建江苏丰山全诺新能源科技有限公司,该公司经营范围包括电解液(锂盐、钠盐等)研发、生产、销售、技术服务。", - "sub_tag": "固态电池", - "sub_tag_reason": "2024年3月15日回复称,丰山全诺正积极研究开发半固态电解液。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300552", - "name": "万集科技", - "main_tag": "AI", - "main_tag_reason": "来自行业:软件开发", - "sub_tag": "无人驾驶", - "sub_tag_reason": "2023年11月21日回复称,公司路侧感知系统已覆盖12个省份193个点位,单日单点位数据量高达1.15T。作为垂直领域智能交通厂商,公司兼具行业know-how与数字化技术,将垂直场景数据的价值最大化,有望实现商业模式升级。通过完整的数据中台架构,公司实现了从边端管理、数据采集、汇聚、清洗、标注、管理、存储到脱敏的闭环管理,同时还可持续扩展以满足不断增长的数据业务需求,从而高效挖掘数据价值并开发数据产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300261", - "name": "雅本化学", - "main_tag": "医药", - "main_tag_reason": "来自行业:农药兽药", - "sub_tag": "长寿药", - "sub_tag_reason": "2020年7月3日回复称NMN项目是公司上虞基地酶制剂及原料药项目之一,目前上虞基地在建设过程中。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_001309", - "name": "德明利", - "main_tag": "半导体", - "main_tag_reason": "来自行业:半导体", - "sub_tag": "存储芯片", - "sub_tag_reason": "2023年02月27日回复称公司在布局研发嵌入式存储时,会根据市场需求和技术趋势,精准定位应用场景,并提供具备差异化竞争优势的解决方案。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_301238", - "name": "瑞泰新材", - "main_tag": "新能源", - "main_tag_reason": "电池材料", - "sub_tag": "固态电池", - "sub_tag_reason": "2022年9月28日回复称公司在固态电池等新型电池方面持续性地进行了相关的研发投入与积累,公司部分产品已作为固态电解质或添加剂进入固态电池客户供应链。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000506", - "name": "中润资源", - "main_tag": "资源", - "main_tag_reason": "来自行业:贵金属", - "sub_tag": "小金属概念", - "sub_tag_reason": "2023年3月27日公司召开第十届董事会第十二次会议,通过资产置换置出房地产业务,置入马拉维锆钛砂矿业务,该矿的产品将主要为钛铁矿和锆石矿。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300890", - "name": "翔丰华", - "main_tag": "新能源", - "main_tag_reason": "来自行业:电池", - "sub_tag": "固态电池", - "sub_tag_reason": "2022年8月8日回复称公司已与清陶能源签署战略合作协议,双方约定将在固态/半固态电池高比容负极材料关键技术研发、供应等方面达成全面战略合作。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300618", - "name": "寒锐钴业", - "main_tag": "新能源", - "main_tag_reason": "来自行业:能源金属", - "sub_tag": "小金属概念", - "sub_tag_reason": "公司主要从事金属钴粉及其他钴产品的研发,生产和销售,是具有自主研发和创新能力的高新技术企业,主营产品包含钴粉,电解铜和钴精矿。公司钴粉产品除了国内销售外,还出口国外市场,公司已成为中国和世界钴粉产品的主要供应商之一。公司以钴粉产品为核心,其他钴产品为补充,形成了从原材料钴矿石的开发,收购,到钴矿石的加工,冶炼,直至钴中间产品和钴粉的完整产业流程,是国内少数拥有有色金属钴完整产业链的企业之一。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300322", - "name": "硕贝德", - "main_tag": "消费电子", - "main_tag_reason": "来自行业:消费电子", - "sub_tag": "6G概念", - "sub_tag_reason": "2023年3月9日回复称6G通信行业处于技术研究起步阶段,公司已有预研,其中卫通天线已完成研发。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300438", - "name": "鹏辉能源", - "main_tag": "新能源", - "main_tag_reason": "来自行业:电池", - "sub_tag": "固态电池", - "sub_tag_reason": "2020年2月27日回复称公司有固态电池方面技术储备,相关电池产品的量产要综合考虑市场需求、成本、产业链等多方面因素。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002709", - "name": "天赐材料", - "main_tag": "新能源", - "main_tag_reason": "电池材料", - "sub_tag": "固态电池", - "sub_tag_reason": "2020年7月20日回复称公司目前对固态电池技术进行了紧密跟踪,现阶段已开展全固态电池用固态电解质的初步研究,目前已有专利布局。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300073", - "name": "当升科技", - "main_tag": "新能源", - "main_tag_reason": "来自行业:电池", - "sub_tag": "固态电池", - "sub_tag_reason": "2020年12月7日公告显示子公司常州锂电新材料研究院开展富锂锰基、固态电池材料等新一代动力锂电正极材料关键技术及产品研发,提前布局前沿专利技术。\n", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000628", - "name": "高新发展", - "main_tag": "AI", - "main_tag_reason": "拟购买四川华鲲振宇70%股权,国产算力服务器龙头。", - "sub_tag": "算力概念", - "sub_tag_reason": "2023年10月18日,公司发布公告,拟购买四川华鲲振宇70%股权。标的公司华鲲振宇为算力产业企业,主要提供基于数据中心、人工智能处理器的自主品牌计算、存储等系列产品的设计、研发、生产、销售及服务。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_002130", - "name": "沃尔核材", - "main_tag": "AI", - "main_tag_reason": "来自行业:非金属材料", - "sub_tag": "铜缆高速连接", - "sub_tag_reason": "2023年6月16日回复称,公司子公司乐庭智联生产的400G、800G高速通信线为DAC铜电缆,在短距离信号传输方面,具备低功耗、高性价比、高速率等优势,广泛应用于数据中心、服务器、交换机/工业路由器等数据信号传输。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300757", - "name": "罗博特科", - "main_tag": "AI", - "main_tag_reason": "800G光模块设备订单", - "sub_tag": "CPO概念", - "sub_tag_reason": "2023年5月30日回复称参股公司ficonTEC在光模块领域新增的设备订单有超过90%的份额都是应用于800G光模块设备,其中包括耦合和贴装的设备。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300133", - "name": "华策影视", - "main_tag": "文化传媒", - "main_tag_reason": "来自行业:文化传媒", - "sub_tag": "Kimi概念", - "sub_tag_reason": "公司与月之暗面进行了模型接入层面的深度合作。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_300308", - "name": "中际旭创", - "main_tag": "AI", - "main_tag_reason": "光模块龙头", - "sub_tag": "CPO概念", - "sub_tag_reason": "2022年12月15日回复称公司本次发布的的大功率激光器,主要适用于小尺寸可插拔外部光源模块(ELSFP)、硅光光模块和光电共封模块(CPO),该激光器首创了全新激光器结构,目的是舍弃隔离器,简化封装,可以降低上述三种形态的光模块成本。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_601138", - "name": "工业富联", - "main_tag": "AI", - "main_tag_reason": "公司多年来是数家第一梯队云服务商AI服务器(加速器)与AI存储器供应商,产品已经开发至第四代。", - "sub_tag": "算力概念", - "sub_tag_reason": "公司官方公众号显示,公司在云计算服务器出货量持续全球领先,与全球主要服务器品牌商、国内外CSP客户深化合作,推出新一代云计算基础设施解决方案,包括模块化服务器、高效运算(HPC) 等。AI服务器方面,公司多年来为数家全球领先客户AI服务器供应商,公司正与客户合作进行AI Cloud data center的托管服务及运维。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000988", - "name": "华工科技", - "main_tag": "AI", - "main_tag_reason": "光模块应用于数据中心建设", - "sub_tag": "CPO概念", - "sub_tag_reason": "2023年2月6日回复称公司光模块产品的主要客户目前应用的产品以100G-400G为主,已推出800G光硅模块产品,该产品主要应用于超大规模云数据中心建设领域。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sh_603799", - "name": "华友钴业", - "main_tag": "新能源", - "main_tag_reason": "来自行业:能源金属", - "sub_tag": "小金属概念", - "sub_tag_reason": "公司主要从事钴新材料产品的深加工及钴、铜有色金属采、选、冶的业务。公司 主导产品为四氧化三钴、氢氧化钴和硫酸钴等钴产品以及锂电正极材料三元前驱体产 品;由于矿料原料中钴铜、钴镍伴生的特性及业务拓展原因,公司还生产、销售电积 铜、粗铜及电解镍等产品。", - "active_hidden_tags": null - }, - { - "entity_id": "stock_sz_000977", - "name": "浪潮信息", - "main_tag": "AI", - "main_tag_reason": "来自行业:计算机设备", - "sub_tag": "液冷概念", - "sub_tag_reason": "2023年4月21日回复称公司已将“Allin液冷”纳入公司发展战略,目前,公司已拥有300多项液冷技术领域核心专利,已参与制定与发布10余项冷板式液冷、浸没式液冷相关设计技术标准。", - "active_hidden_tags": null } ] \ No newline at end of file diff --git a/api-tests/tag/query_simple_stock_tags.http b/api-tests/tag/query_simple_stock_tags.http index 60689d5a..7ec4c6f3 100644 --- a/api-tests/tag/query_simple_stock_tags.http +++ b/api-tests/tag/query_simple_stock_tags.http @@ -5,6 +5,8 @@ Content-Type: application/json { "entity_ids": [ - "stock_sz_002085" + "stock_sz_002085", + "stock_sz_000099", + "stock_sz_002130" ] -} +} \ No newline at end of file diff --git a/api-tests/tag/query_stock_tags.http b/api-tests/tag/query_stock_tags.http index 3b10e6e8..a1e655d5 100644 --- a/api-tests/tag/query_stock_tags.http +++ b/api-tests/tag/query_stock_tags.http @@ -5,6 +5,8 @@ Content-Type: application/json { "entity_ids": [ - "stock_sz_002085" + "stock_sz_000099", + "stock_sz_002085", + "stock_sz_001696" ] } diff --git a/api-tests/tag/set_stock_tags.http b/api-tests/tag/set_stock_tags.http index a92a6136..39774a3d 100644 --- a/api-tests/tag/set_stock_tags.http +++ b/api-tests/tag/set_stock_tags.http @@ -3,11 +3,15 @@ accept: application/json Content-Type: application/json { - "entity_id": "stock_sz_301091", - "name": "深城交", - "main_tag": "低空经济", - "main_tag_reason": "2024年3月8日回复称,目前公司已与另外一家深圳研究咨询机构组成的联合体正式承接了深圳低空智能融合基础设施建设项目一期项目,该项目主要围绕深圳市低空经济发展,建设可覆盖全市范围的智能融合系统的软件平台(包括低空操作管理系统和低空管理服务系统),建设配套的管服中心、数据中心及无人机测试场,接入典型的城市场景,并进行软件平台的验证。", - "sub_tag": "低空经济", - "sub_tag_reason": "2024年3月8日回复称,目前公司已与另外一家深圳研究咨询机构组成的联合体正式承接了深圳低空智能融合基础设施建设项目一期项目,该项目主要围绕深圳市低空经济发展,建设可覆盖全市范围的智能融合系统的软件平台(包括低空操作管理系统和低空管理服务系统),建设配套的管服中心、数据中心及无人机测试场,接入典型的城市场景,并进行软件平台的验证。", - "active_hidden_tags": null -} \ No newline at end of file + "entity_id": "stock_sz_001366", + "name": "播恩集团", + "main_tag": "医药", + "main_tag_reason": "合成生物概念", + "main_tags": { + "农业": "来自行业:农牧饲渔" + }, + "sub_tag": "生物医药", + "sub_tag_reason": "合成生物概念", + "sub_tags": null, + "active_hidden_tags": null + } \ No newline at end of file diff --git a/api-tests/trading/query_stock_quotes.http b/api-tests/trading/query_stock_quotes.http new file mode 100644 index 00000000..d30601e5 --- /dev/null +++ b/api-tests/trading/query_stock_quotes.http @@ -0,0 +1,9 @@ +POST http://127.0.0.1:8090/api/trading/query_stock_quotes +accept: application/json +Content-Type: application/json + +{ + "tag": "材料", + "limit": 500, + "order_by_field": "change_pct" +} diff --git a/examples/utils.py b/examples/utils.py index 9ea107ab..246e9bce 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -6,7 +6,7 @@ import eastmoneypy import pandas as pd - +import requests from zvt.domain import StockNews, Stock, LimitUpInfo from zvt.utils import date_time_by_interval, today @@ -14,31 +14,33 @@ def add_to_eastmoney(codes, group, entity_type="stock", over_write=True): - codes = list(set(codes)) - if over_write: + with requests.Session() as session: + codes = list(set(codes)) + if over_write: + try: + eastmoneypy.del_group(group_name=group, session=session) + except: + pass try: - eastmoneypy.del_group(group_name=group) + eastmoneypy.create_group(group_name=group, session=session) except: pass - try: - eastmoneypy.create_group(group_name=group) - except: - pass - group_id = eastmoneypy.get_group_id(group) + group_id = eastmoneypy.get_group_id(group, session=session) - for code in codes: - time.sleep(1) - eastmoneypy.add_to_group(code=code, entity_type=entity_type, group_id=group_id) + for code in codes: + eastmoneypy.add_to_group(code=code, entity_type=entity_type, group_id=group_id, session=session) def clean_groups(keep=None): if keep is None: keep = ["自选股", "练气", "重要板块", "主线"] - groups = eastmoneypy.get_groups() - groups_to_clean = [group["gid"] for group in groups if group["gname"] not in keep] - for gid in groups_to_clean: - eastmoneypy.del_group(group_id=gid) + + with requests.Session() as session: + groups = eastmoneypy.get_groups(session=session) + groups_to_clean = [group["gid"] for group in groups if group["gname"] not in keep] + for gid in groups_to_clean: + eastmoneypy.del_group(group_id=gid, session=session) def get_hot_words_config(): diff --git a/scripts/z.sh b/examples/z.sh similarity index 100% rename from scripts/z.sh rename to examples/z.sh diff --git a/scripts/qmt_runner.py b/scripts/qmt_runner.py deleted file mode 100644 index 3605d246..00000000 --- a/scripts/qmt_runner.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -from zvt.utils.recorder_utils import run_data_recorder -from zvt.api.selector import get_entity_ids_by_filter -from zvt.domain import Stock1dHfqKdata - -if __name__ == "__main__": - data_provider = "qmt" - sleeping_time = 0 - normal_stock_ids = get_entity_ids_by_filter( - provider="exchange", ignore_delist=True, ignore_st=False, ignore_new_stock=False - ) - - run_data_recorder( - entity_provider="exchange", - entity_ids=normal_stock_ids, - day_data=False, - domain=Stock1dHfqKdata, - data_provider=data_provider, - force_update=False, - sleeping_time=sleeping_time, - return_unfinished=True, - ) diff --git a/scripts/report_stock.py b/scripts/report_stock.py deleted file mode 100644 index 25d44228..00000000 --- a/scripts/report_stock.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from examples.data_runner.kdata_runner import record_stock_data -from examples.reports.report_tops import report_top_stocks -from examples.reports.report_vol_up import report_vol_up_stocks -from zvt.factors.top_stocks import compute_top_stocks - -if __name__ == "__main__": - record_stock_data() - compute_top_stocks() - - report_top_stocks() - report_vol_up_stocks() diff --git a/scripts/report_stockhk.py b/scripts/report_stockhk.py deleted file mode 100644 index dcc58f09..00000000 --- a/scripts/report_stockhk.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from examples.data_runner.kdata_runner import record_stockhk_data -from examples.reports.report_tops import report_top_stockhks -from examples.reports.report_vol_up import report_vol_up_stockhks - -if __name__ == "__main__": - record_stockhk_data() - - report_top_stockhks() - report_vol_up_stockhks() diff --git a/src/fill_project.py b/src/fill_project.py new file mode 100644 index 00000000..54e3490f --- /dev/null +++ b/src/fill_project.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from zvt.autocode import gen_exports + +if __name__ == "__main__": + gen_exports(dir_path="./zvt_vip/dataset") + gen_exports(dir_path="./zvt_vip/recorders") + gen_exports(dir_path="./zvt_vip/tag") diff --git a/src/zvt/__init__.py b/src/zvt/__init__.py index e96ffe5f..0fa81023 100644 --- a/src/zvt/__init__.py +++ b/src/zvt/__init__.py @@ -217,10 +217,15 @@ def old_db_to_provider_dir(data_path): import zvt.recorders as zvt_recorders import zvt.factors as zvt_factors -# try: -# import zvt.recorders.qmt.quotes.qmt_kdata_recorder as qmt_kdata_recorde -# except Exception as e: -# logger.warning("QMT need run in Windows!", e) +import platform + +if platform.system() == "Windows": + try: + import zvt.recorders.qmt.quotes.qmt_kdata_recorder as qmt_kdata_recorde + except Exception as e: + logger.error("QMT not work", e) +else: + logger.warning("QMT need run in Windows!") __all__ = ["zvt_env", "zvt_config", "init_log", "init_env", "init_config", "__version__"] diff --git a/src/zvt/broker/qmt/context.py b/src/zvt/broker/qmt/context.py new file mode 100644 index 00000000..79a52151 --- /dev/null +++ b/src/zvt/broker/qmt/context.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from typing import Optional + +from zvt import zvt_env +from zvt.broker.qmt.qmt_account import QmtStockAccount + + +class QmtContext(object): + def __init__(self): + self.qmt_account: Optional[QmtStockAccount] = None + + +qmt_context = QmtContext() + + +def init_qmt_account(qmt_mini_data_path=None, qmt_account_id=None): + if not qmt_mini_data_path: + qmt_mini_data_path = zvt_env["qmt_mini_data_path"] + if not qmt_account_id: + qmt_account_id = zvt_env["qmt_account_id"] + qmt_context.qmt_account = QmtStockAccount( + path=qmt_mini_data_path, account_id=qmt_account_id, trader_name="zvt", session_id=None + ) + + +init_qmt_account() diff --git a/src/zvt/broker/qmt/errors.py b/src/zvt/broker/qmt/errors.py new file mode 100644 index 00000000..3c002ad1 --- /dev/null +++ b/src/zvt/broker/qmt/errors.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +class TraderError(Exception): + """Base class for exceptions in this module.""" + + pass + + +class QmtError(TraderError): + def __init__(self, message="qmt error"): + self.message = message + + +class PositionOverflowError(TraderError): + def __init__(self, message="超出仓位限制"): + self.message = message diff --git a/src/zvt/broker/qmt/qmt_account.py b/src/zvt/broker/qmt/qmt_account.py index bb297463..dbb6aa43 100644 --- a/src/zvt/broker/qmt/qmt_account.py +++ b/src/zvt/broker/qmt/qmt_account.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- import logging import time -import sys - from typing import List from xtquant import xtconstant, xtdata from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback from xtquant.xttype import StockAccount, XtPosition -from zvt.broker.qmt import qmt_api -from zvt.broker.qmt.qmt_api import _to_qmt_code + +from zvt.broker.qmt.errors import QmtError, PositionOverflowError +from zvt.broker.qmt.qmt_quote import _to_qmt_code +from zvt.common.trading_models import BuyPositionStrategy, PositionType, SellPositionStrategy from zvt.trader import AccountService, TradingSignal, OrderType, trading_signal_type_to_order_type from zvt.utils import now_pd_timestamp, to_pd_timestamp @@ -26,15 +26,12 @@ def _to_qmt_order_type(order_type: OrderType): class MyXtQuantTraderCallback(XtQuantTraderCallback): def on_connected(self): - super().on_connected() logger.info("qmt on_connected") def on_smt_appointment_async_response(self, response): - super().on_smt_appointment_async_response(response) logger.info(f"qmt on_smt_appointment_async_response: {vars(response)}") def on_cancel_order_stock_async_response(self, response): - super().on_cancel_order_stock_async_response(response) logger.info(f"qmt on_cancel_order_stock_async_response: {vars(response)}") def on_disconnected(self): @@ -50,7 +47,6 @@ def on_stock_order(self, order): :param order: XtOrder对象 :return: """ - super().on_stock_order(order) logger.info(f"qmt on_stock_order: {vars(order)}") def on_stock_asset(self, asset): @@ -59,7 +55,6 @@ def on_stock_asset(self, asset): :param asset: XtAsset对象 :return: """ - super().on_stock_asset(asset) logger.info(f"qmt on_stock_asset: {vars(asset)}") def on_stock_trade(self, trade): @@ -68,7 +63,6 @@ def on_stock_trade(self, trade): :param trade: XtTrade对象 :return: """ - super().on_stock_trade(trade) logger.info(f"qmt on_stock_trade: {vars(trade)}") def on_stock_position(self, position): @@ -77,7 +71,6 @@ def on_stock_position(self, position): :param position: XtPosition对象 :return: """ - super().on_stock_position(position) logger.info(f"qmt on_stock_position: {vars(position)}") def on_order_error(self, order_error): @@ -86,7 +79,6 @@ def on_order_error(self, order_error): :param order_error:XtOrderError 对象 :return: """ - super().on_order_error(order_error) logger.info(f"qmt on_order_error: {vars(order_error)}") def on_cancel_error(self, cancel_error): @@ -95,7 +87,6 @@ def on_cancel_error(self, cancel_error): :param cancel_error: XtCancelError 对象 :return: """ - super().on_cancel_error(cancel_error) logger.info(f"qmt on_cancel_error: {vars(cancel_error)}") def on_order_stock_async_response(self, response): @@ -104,7 +95,6 @@ def on_order_stock_async_response(self, response): :param response: XtOrderResponse 对象 :return: """ - super().on_order_stock_async_response(response) logger.info(f"qmt on_order_stock_async_response: {vars(response)}") def on_account_status(self, status): @@ -112,7 +102,6 @@ def on_account_status(self, status): :param response: XtAccountStatus 对象 :return: """ - logger.info("on_account_status") logger.info(status.account_id, status.account_type, status.status) @@ -123,7 +112,7 @@ def __init__(self, path, account_id, trader_name, session_id=None) -> None: self.trader_name = trader_name logger.info(f"path: {path}, account: {account_id}, trader_name: {trader_name}, session: {session_id}") - self.xt_trader = XtQuantTrader(path, session_id) + self.xt_trader = XtQuantTrader(path=path, session=session_id) # StockAccount可以用第二个参数指定账号类型,如沪港通传'HUGANGTONG',深港通传'SHENGANGTONG' self.account = StockAccount(account_id=account_id, account_type="STOCK") @@ -138,15 +127,16 @@ def __init__(self, path, account_id, trader_name, session_id=None) -> None: # 建立交易连接,返回0表示连接成功 connect_result = self.xt_trader.connect() if connect_result != 0: - logger.error(f"连接失败: {connect_result}") - sys.exit(f"连接失败: {connect_result}") - logger.info("建立交易连接成功!") + logger.error(f"qmt trader 连接失败: {connect_result}") + raise QmtError(f"qmt trader 连接失败: {connect_result}") + logger.info("qmt trader 建立交易连接成功!") # 对交易回调进行订阅,订阅后可以收到交易主推,返回0表示订阅成功 subscribe_result = self.xt_trader.subscribe(self.account) + if subscribe_result != 0: logger.error(f"账号订阅失败: {subscribe_result}") - sys.exit(f"账号订阅失败: {subscribe_result}") + raise QmtError(f"账号订阅失败: {subscribe_result}") logger.info("账号订阅成功!") def get_positions(self): @@ -225,6 +215,80 @@ def on_trading_finish(self, timestamp): def on_trading_error(self, timestamp, error): pass + def sell(self, position_strategy: SellPositionStrategy): + # account_type int 账号类型,参见数据字典 + # account_id str 资金账号 + # stock_code str 证券代码 + # volume int 持仓数量 + # can_use_volume int 可用数量 + # open_price float 开仓价 + # market_value float 市值 + # frozen_volume int 冻结数量 + # on_road_volume int 在途股份 + # yesterday_volume int 昨夜拥股 + # avg_price float 成本价 + # direction int 多空方向,股票不适用;参见数据字典 + stock_codes = [_to_qmt_code(entity_id) for entity_id in position_strategy.entity_ids] + for i, stock_code in enumerate(stock_codes): + pct = position_strategy.sell_pcts[i] + position = self.xt_trader.query_stock_position(self.account, stock_code) + fix_result_order_id = self.xt_trader.order_stock( + account=self.account, + stock_code=stock_code, + order_type=xtconstant.STOCK_SELL, + order_volume=int(position.can_use_volume * pct), + price_type=xtconstant.MARKET_SH_CONVERT_5_CANCEL, + price=0, + strategy_name=self.trader_name, + order_remark="order from zvt", + ) + logger.info(f"order result id: {fix_result_order_id}") + + def buy(self, position_strategy: BuyPositionStrategy): + # account_type int 账号类型,参见数据字典 + # account_id str 资金账号 + # cash float 可用金额 + # frozen_cash float 冻结金额 + # market_value float 持仓市值 + # total_asset float 总资产 + acc = self.get_current_account() + # 检查仓位 + if position_strategy.position_type == PositionType.normal: + current_pct = round(acc.market_value / acc.total_asset, 2) + if current_pct >= position_strategy.position_pct: + raise PositionOverflowError(f"目前仓位为{current_pct}, 已超过请求的仓位: {position_strategy.position_pct}") + + money_to_use = acc.total_asset * (position_strategy.position_pct - current_pct) + elif position_strategy.position_type == PositionType.cash: + money_to_use = acc.cash * position_strategy.position_pct + else: + assert False + + stock_codes = [_to_qmt_code(entity_id) for entity_id in position_strategy.entity_ids] + ticks = xtdata.get_full_tick(code_list=stock_codes) + + if not position_strategy.weights: + stocks_count = len(stock_codes) + money_for_stocks = [round(money_to_use / stocks_count)] * stocks_count + else: + weights_sum = sum(position_strategy.weights) + money_for_stocks = [round(weight / weights_sum) for weight in position_strategy.weights] + + for i, stock_code in enumerate(stock_codes): + try_price = ticks[stock_code]["askPrice"][3] + volume = money_for_stocks[i] / try_price + fix_result_order_id = self.xt_trader.order_stock( + account=self.account, + stock_code=stock_code, + order_type=xtconstant.STOCK_BUY, + order_volume=volume, + price_type=xtconstant.MARKET_SH_CONVERT_5_CANCEL, + price=0, + strategy_name=self.trader_name, + order_remark="order from zvt", + ) + logger.info(f"order result id: {fix_result_order_id}") + if __name__ == "__main__": account = QmtStockAccount(path=r"D:\qmt\userdata_mini", account_id="") diff --git a/src/zvt/broker/qmt/qmt_api.py b/src/zvt/broker/qmt/qmt_api.py deleted file mode 100644 index 8bf179a4..00000000 --- a/src/zvt/broker/qmt/qmt_api.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -from datetime import datetime - -import pandas as pd -from xtquant import xtdata - -from zvt.contract import IntervalLevel, AdjustType -from zvt.contract.api import decode_entity_id, df_to_db -from zvt.domain import StockQuotes, Stock -from zvt.utils import to_time_str, current_date, to_pd_timestamp - - -# https://dict.thinktrader.net/nativeApi/start_now.html?id=e2M5nZ - -logger = logging.getLogger(__name__) - - -def _to_qmt_code(entity_id): - _, exchange, code = decode_entity_id(entity_id=entity_id) - return f"{code}.{exchange.upper()}" - - -def _to_zvt_entity_id(qmt_code): - code, exchange = qmt_code.split(".") - exchange = exchange.lower() - return f"stock_{exchange}_{code}" - - -def _to_qmt_dividend_type(adjust_type: AdjustType): - if adjust_type == AdjustType.qfq: - return "front" - elif adjust_type == AdjustType.hfq: - return "back" - else: - return "none" - - -def _qmt_instrument_detail_to_stock(stock_detail): - exchange = stock_detail["ExchangeID"].lower() - code = stock_detail["InstrumentID"] - name = stock_detail["InstrumentName"] - list_date = to_pd_timestamp(stock_detail["OpenDate"]) - end_date = to_pd_timestamp(stock_detail["ExpireDate"]) - pre_close = stock_detail["PreClose"] - limit_up_price = stock_detail["UpStopPrice"] - limit_down_price = stock_detail["DownStopPrice"] - float_volume = stock_detail["FloatVolume"] - total_volume = stock_detail["TotalVolume"] - - entity_id = f"stock_{exchange}_{code}" - - return { - "id": entity_id, - "entity_id": entity_id, - "timestamp": list_date, - "entity_type": "stock", - "exchange": exchange, - "code": code, - "name": name, - "list_date": list_date, - "end_date": end_date, - "pre_close": pre_close, - "limit_up_price": limit_up_price, - "limit_down_price": limit_down_price, - "float_volume": float_volume, - "total_volume": total_volume, - } - - -def get_entity_list(): - stocks = xtdata.get_stock_list_in_sector("沪深A股") - entity_list = [] - for stock in stocks: - stock_detail = xtdata.get_instrument_detail(stock, False) - entity_list.append(_qmt_instrument_detail_to_stock(stock_detail)) - - return pd.DataFrame.from_records(data=entity_list) - - -def get_kdata( - entity_id, - start_timestamp, - end_timestamp, - level=IntervalLevel.LEVEL_1DAY, - adjust_type=AdjustType.qfq, -): - code = _to_qmt_code(entity_id=entity_id) - period = level.value - # 保证qmt先下载数据到本地 - xtdata.download_history_data(stock_code=code, period=period, start_time="", end_time="") - records = xtdata.get_market_data( - stock_list=[code], - period=period, - start_time=to_time_str(start_timestamp, fmt="YYYYMMDDHHmmss"), - end_time=to_time_str(end_timestamp, fmt="YYYYMMDDHHmmss"), - dividend_type=_to_qmt_dividend_type(adjust_type=adjust_type), - fill_data=False, - ) - - dfs = [] - for col in records: - df = records[col].T - df.columns = [col] - dfs.append(df) - return pd.concat(dfs, axis=1) - - -def _tick_to_quotes(): - pass - - -def record_tick(): - stocks = xtdata.get_stock_list_in_sector("沪深A股") - logger.info(f"today stocks[{len(stocks)}]:{stocks}") - - df = Stock.query_data(provider="em", return_type="df", index="entity_id") - - def on_data(datas, stock_df=df): - dt = datetime.now() - var = { - "time": 1714460254000, - "lastPrice": 36.08, - "open": 35.4, - "high": 36.62, - "low": 35.13, - "lastClose": 34.1, - "amount": 67690300.0, - "volume": 18793, - "pvolume": 1879308, - "stockStatus": 0, - "openInt": 18, - "transactionNum": 0, - "lastSettlementPrice": 0.0, - "settlementPrice": 0.0, - "pe": 0.0, - "askPrice": [36.07, 0.0, 0.0, 0.0, 0.0], - "bidPrice": [36.07, 0.0, 0.0, 0.0, 0.0], - "askVol": [14, 0, 0, 0, 0], - "bidVol": [14, 12, 0, 0, 0], - "volRatio": 0.0, - "speed1Min": 0.0, - "speed5Min": 0.0, - } - # #: 是否涨停 - # is_limit_up = Column(Boolean) - # #: 封涨停金额 - # limit_up_amount = Column(Float) - # #: 是否跌停 - # is_limit_down = Column(Boolean) - # #: 封跌停金额 - # limit_down_amount = Column(Float) - # #: 5挡卖单金额 - # ask_amount = Column(Float) - # #: 5挡买单金额 - # bid_amount = Column(Float) - # #: 流通市值 - # float_cap = Column(Float) - # #: 总市值 - # total_cap = Column(Float) - tick_df = pd.DataFrame.from_dict(datas, orient="index") - tick_df.index = tick_df.index.map(_to_zvt_entity_id) - - meta_df = stock_df.loc[tick_df.index] - df = pd.concat([tick_df, meta_df], axis=1) - - df = df.rename(columns={"lastPrice": "price", "amount": "turnover"}) - df["timestamp"] = pd.to_datetime(df["time"], unit="ms") - df["turnover_rate"] = df["pvolume"] - df["change_pct"] = (df["price"] - df["lastClose"]) / df["lastClose"] - df["ask_amount"] = (df["askPrice"] * df["askVol"]).sum(axis=1) - df["bid_amount"] = (df["bidPrice"] * df["bidVol"]).sum(axis=1) - print(df) - df_to_db(df=df, data_schema=StockQuotes, provider="qmt") - - xtdata.subscribe_whole_quote(["SH", "SZ"], callback=on_data) - xtdata.run() - - -def record_kdata(entity_ids): - - stock_list = [_to_qmt_code(entity_id=entity_id) for entity_id in entity_ids] - now = datetime.now() - start_time = datetime(year=now.year, month=now.month, day=now.day, hour=9, minute=20, second=0) - start_time = to_time_str(start_time, fmt="YYYYMMDDHHmmss") - - end_time = datetime(year=now.year, month=now.month, day=now.day, hour=15, minute=0, second=0) - end_time = to_time_str(end_time, fmt="YYYYMMDDHHmmss") - while True: - records = xtdata.get_local_data( - stock_list=stock_list, - period="tick", - start_time=start_time, - end_time=end_time, - fill_data=False, - ) - datas = records.get(stock_list[0]) - start_time = to_time_str(to_pd_timestamp(int(datas[-1][0])), fmt="YYYYMMDDHHmmss") - logger.info(f"to {start_time}") - - if start_time == end_time: - logger.info("finished") - break - - -if __name__ == "__main__": - print(get_kdata(entity_id="stock_sz_000001", start_timestamp="20230101", end_timestamp="20230329")) - # 'time' #时间戳 - # 'lastPrice' #最新价 - # 'open' #开盘价 - # 'high' #最高价 - # 'low' #最低价 - # 'lastClose' #前收盘价 - # 'amount' #成交总额 - # 'volume' #成交总量 - # 'pvolume' #原始成交总量 - # 'stockStatus' #证券状态 - # 'openInt' #持仓量 - # 'lastSettlementPrice' #前结算 - # 'askPrice' #委卖价 - # 'bidPrice' #委买价 - # 'askVol' #委卖量 - # 'bidVol' #委买量 - # 'transactionNum' #成交笔数 - { - "000001.SZ": { - "timetag": "20240425 14:57:36", - "lastPrice": 10.61, - "open": 10.5, - "high": 10.62, - "low": 10.48, - "lastClose": 10.53, - "amount": 1161898300, - "volume": 1100234, - "pvolume": 110023424, - "stockStatus": 0, - "openInt": 18, - "settlementPrice": 0, - "lastSettlementPrice": 0, - "askPrice": [10.6, 0, 0, 0, 0], - "bidPrice": [10.6, 0, 0, 0, 0], - "askVol": [3349, 0, 0, 0, 0], - "bidVol": [3349, 2600, 0, 0, 0], - }, - "000002.SZ": { - "timetag": "20240425 14:57:36", - "lastPrice": 6.55, - "open": 6.5, - "high": 6.68, - "low": 6.48, - "lastClose": 6.54, - "amount": 1242969100, - "volume": 1889977, - "pvolume": 188997683, - "stockStatus": 0, - "openInt": 18, - "settlementPrice": 0, - "lastSettlementPrice": 0, - "askPrice": [6.55, 0, 0, 0, 0], - "bidPrice": [6.55, 0, 0, 0, 0], - "askVol": [4030, 1032, 0, 0, 0], - "bidVol": [4030, 0, 0, 0, 0], - }, - } -# [(1713834900000, 0. , 0., 0. , 0. , 44.96, 0.00000000e+00, 0, 0, 0, 12, 0., list([44.95, 0.0, 0.0, 0.0, 0.0]), list([44.95, 0.0, 0.0, 0.0, 0.0]), list([8, 3, 0, 0, 0]), list([8, 0, 0, 0, 0]), 0., 0, 0.), -# (1713834909000, 0. , 0., 0. , 0. , 44.96, 0.00000000e+00, 0, 0, 0, 12, 0., list([44.95, 0.0, 0.0, 0.0, 0.0]), list([44.95, 0.0, 0.0, 0.0, 0.0]), list([13, 1, 0, 0, 0]), list([13, 0, 0, 0, 0]), 0., 0, 0.), -# diff --git a/src/zvt/broker/qmt/qmt_quote.py b/src/zvt/broker/qmt/qmt_quote.py new file mode 100644 index 00000000..2474665b --- /dev/null +++ b/src/zvt/broker/qmt/qmt_quote.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +import json +import logging +import time +import datetime +import numpy as np +import pandas as pd +from xtquant import xtdata +from sqlalchemy import text +from zvt.api import get_recent_report_date +from zvt.common.query_models import TimeUnit +from zvt.contract import IntervalLevel, AdjustType +from zvt.contract.api import decode_entity_id, df_to_db, get_db_engine, get_schema_columns, get_db_session +from zvt.domain import StockQuote, Stock +from zvt.utils import ( + to_time_str, + current_date, + to_pd_timestamp, + now_time_str, + date_time_by_interval, + pd_is_not_null, + TIME_FORMAT_DAY, + TIME_FORMAT_MINUTE2, +) + +from sqlalchemy.dialects.sqlite import insert + +# https://dict.thinktrader.net/nativeApi/start_now.html?id=e2M5nZ + +logger = logging.getLogger(__name__) + + +def _to_qmt_code(entity_id): + _, exchange, code = decode_entity_id(entity_id=entity_id) + return f"{code}.{exchange.upper()}" + + +def _to_zvt_entity_id(qmt_code): + code, exchange = qmt_code.split(".") + exchange = exchange.lower() + return f"stock_{exchange}_{code}" + + +def _to_qmt_dividend_type(adjust_type: AdjustType): + if adjust_type == AdjustType.qfq: + return "front" + elif adjust_type == AdjustType.hfq: + return "back" + else: + return "none" + + +def _qmt_instrument_detail_to_stock(stock_detail): + exchange = stock_detail["ExchangeID"].lower() + code = stock_detail["InstrumentID"] + name = stock_detail["InstrumentName"] + list_date = to_pd_timestamp(stock_detail["OpenDate"]) + end_date = to_pd_timestamp(stock_detail["ExpireDate"]) + pre_close = stock_detail["PreClose"] + limit_up_price = stock_detail["UpStopPrice"] + limit_down_price = stock_detail["DownStopPrice"] + float_volume = stock_detail["FloatVolume"] + total_volume = stock_detail["TotalVolume"] + + entity_id = f"stock_{exchange}_{code}" + + return { + "id": entity_id, + "entity_id": entity_id, + "timestamp": list_date, + "entity_type": "stock", + "exchange": exchange, + "code": code, + "name": name, + "list_date": list_date, + "end_date": end_date, + "pre_close": pre_close, + "limit_up_price": limit_up_price, + "limit_down_price": limit_down_price, + "float_volume": float_volume, + "total_volume": total_volume, + } + + +def get_qmt_stocks(): + return xtdata.get_stock_list_in_sector("沪深A股") + + +def get_entity_list(): + stocks = get_qmt_stocks() + entity_list = [] + + for stock in stocks: + stock_detail = xtdata.get_instrument_detail(stock, False) + if stock_detail: + entity_list.append(_qmt_instrument_detail_to_stock(stock_detail)) + else: + code, exchange = stock.split(".") + exchange = exchange.lower() + entity_id = f"stock_{exchange}_{code}" + # get from provider exchange + datas = Stock.query_data(provider="em", entity_id=entity_id, return_type="dict") + if datas: + entity = datas[0] + else: + entity = { + "id": _to_zvt_entity_id(stock), + "entity_id": entity_id, + "entity_type": "stock", + "exchange": exchange, + "code": code, + "name": "未获取", + } + + # xtdata.download_financial_data(stock_list=[stock], table_list=["Capital"]) + capital_datas = xtdata.get_financial_data( + [stock], + table_list=["Capital"], + report_type="report_time", + ) + df = capital_datas[stock]["Capital"] + if pd_is_not_null(df): + latest_data = df.iloc[-1] + entity["float_volume"] = latest_data["circulating_capital"] + entity["total_volume"] = latest_data["total_capital"] + + tick = xtdata.get_full_tick(code_list=[stock]) + if tick and tick[stock]: + if code.startswith("300") or code.startswith("688"): + limit_up_price = tick[stock]["lastClose"] * 1.2 + limit_down_price = tick[stock]["lastClose"] * 0.8 + else: + limit_up_price = tick[stock]["lastClose"] * 1.1 + limit_down_price = tick[stock]["lastClose"] * 0.9 + entity["limit_up_price"] = round(limit_up_price, 2) + entity["limit_down_price"] = round(limit_down_price, 2) + entity_list.append(entity) + + return pd.DataFrame.from_records(data=entity_list) + + +def get_kdata( + entity_id, + start_timestamp, + end_timestamp, + level=IntervalLevel.LEVEL_1DAY, + adjust_type=AdjustType.qfq, +): + code = _to_qmt_code(entity_id=entity_id) + period = level.value + # 保证qmt先下载数据到本地 + xtdata.download_history_data(stock_code=code, period=period, start_time="", end_time="") + records = xtdata.get_market_data( + stock_list=[code], + period=period, + start_time=to_time_str(start_timestamp, fmt="YYYYMMDDHHmmss"), + end_time=to_time_str(end_timestamp, fmt="YYYYMMDDHHmmss"), + dividend_type=_to_qmt_dividend_type(adjust_type=adjust_type), + fill_data=False, + ) + + dfs = [] + for col in records: + df = records[col].T + df.columns = [col] + dfs.append(df) + return pd.concat(dfs, axis=1) + + +def tick_to_quote(): + entity_list = get_entity_list() + entity_df = entity_list[ + ["entity_id", "code", "name", "limit_up_price", "limit_down_price", "float_volume", "total_volume"] + ] + entity_df = entity_df.set_index("entity_id", drop=False) + + def calculate_limit_up_amount(row): + if row["is_limit_up"]: + return row["price"] * row["bidVol"][0] * 100 + else: + return None + + def calculate_limit_down_amount(row): + if row["is_limit_down"]: + return row["price"] * row["askVol"][0] * 100 + else: + return None + + def on_data(datas, stock_df=entity_df): + start_time = time.time() + + tick_df = pd.DataFrame.from_records(data=[datas[code] for code in datas], index=list(datas.keys())) + tick_df.index = tick_df.index.map(_to_zvt_entity_id) + + df = pd.concat( + [ + stock_df.loc[ + tick_df.index, + ], + tick_df, + ], + axis=1, + ) + + df = df.rename(columns={"lastPrice": "price", "amount": "turnover"}) + + df["timestamp"] = df["time"].apply(to_pd_timestamp) + + df["id"] = df[["entity_id", "timestamp"]].apply( + lambda se: "{}_{}".format(se["entity_id"], to_time_str(se["timestamp"])), axis=1 + ) + + # 换手率 + df["turnover_rate"] = df["pvolume"] / df["float_volume"] + # 涨跌幅 + df["change_pct"] = (df["price"] - df["lastClose"]) / df["lastClose"] + # 盘口卖单金额 + df["ask_amount"] = df.apply( + lambda row: np.sum(np.array(row["askPrice"]) * (np.array(row["askVol"]) * 100)), axis=1 + ) + # 盘口买单金额 + df["bid_amount"] = df.apply( + lambda row: np.sum(np.array(row["bidPrice"]) * (np.array(row["bidVol"]) * 100)), axis=1 + ) + # 涨停 + df["is_limit_up"] = (df["price"] != 0) & (df["price"] >= df["limit_up_price"]) + df["limit_up_amount"] = df.apply(lambda row: calculate_limit_up_amount(row), axis=1) + + # 跌停 + df["is_limit_down"] = (df["price"] != 0) & (df["price"] <= df["limit_down_price"]) + df["limit_down_amount"] = df.apply(lambda row: calculate_limit_down_amount(row), axis=1) + + df["float_cap"] = df["float_volume"] * df["price"] + df["total_cap"] = df["total_volume"] * df["price"] + + df_to_db(df, data_schema=StockQuote, provider="qmt", force_update=True, drop_duplicates=False) + cost_time = time.time() - start_time + logger.info(f"Quotes cost_time:{cost_time} for {len(datas.keys())} stocks") + + return on_data + + +def download_capital_data(): + stocks = get_qmt_stocks() + xtdata.download_financial_data2( + stock_list=stocks, table_list=["Capital"], start_time="", end_time="", callback=lambda x: print(x) + ) + + +if __name__ == "__main__": + Stock.record_data(provider="em") + stocks = get_qmt_stocks() + print(stocks) + xtdata.subscribe_whole_quote(stocks, callback=tick_to_quote()) + xtdata.run() diff --git a/src/zvt/common/query_models.py b/src/zvt/common/query_models.py index f4c44aa1..f0627c69 100644 --- a/src/zvt/common/query_models.py +++ b/src/zvt/common/query_models.py @@ -6,6 +6,11 @@ from pydantic import BaseModel, Field +class OrderByType(Enum): + asc = "asc" + desc = "desc" + + class TimeUnit(Enum): year = "year" month = "month" diff --git a/src/zvt/common/trading_models.py b/src/zvt/common/trading_models.py new file mode 100644 index 00000000..76970c03 --- /dev/null +++ b/src/zvt/common/trading_models.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel, Field + + +class PositionType(Enum): + # 按整体仓位算 + normal = "normal" + + # 不管整体仓位多少 + # 按现金算 + cash = "cash" + + +class BuyPositionStrategy(BaseModel): + entity_ids: List[str] + position_type: PositionType = Field(default=PositionType.normal) + position_pct: float + weights: Optional[List[float]] = Field(default=None) + + +class SellPositionStrategy(BaseModel): + entity_ids: List[str] + sell_pcts: Optional[List[float]] = Field(default=None) diff --git a/src/zvt/config.json b/src/zvt/config.json index 6080e43c..980c95df 100644 --- a/src/zvt/config.json +++ b/src/zvt/config.json @@ -8,5 +8,7 @@ "email_username": "", "email_password": "", "wechat_app_id": "", - "wechat_app_secrect": "" + "wechat_app_secrect": "", + "qmt_mini_data_path": "D:\\qmt\\userdata_mini", + "qmt_account_id": "" } \ No newline at end of file diff --git a/src/zvt/contract/api.py b/src/zvt/contract/api.py index 56a505bb..9d32176f 100644 --- a/src/zvt/contract/api.py +++ b/src/zvt/contract/api.py @@ -497,8 +497,6 @@ def df_to_db( logger.warning(f"remove duplicated:{df[df.duplicated()]}") df = df.drop_duplicates(subset="id", keep="last") - db_engine = get_db_engine(provider, data_schema=data_schema) - schema_cols = get_schema_columns(data_schema) cols = set(df.columns.tolist()) & set(schema_cols) @@ -523,36 +521,36 @@ def df_to_db( saved = 0 - for step in range(step_size): - df_current = df.iloc[sub_size * step : sub_size * (step + 1)] + db_engine = get_db_engine(provider, data_schema=data_schema) + with db_engine.connect() as conn: + for step in range(step_size): + df_current = df.iloc[sub_size * step : sub_size * (step + 1)] + + session = get_db_session(provider=provider, data_schema=data_schema) + if force_update: + ids = df_current["id"].tolist() + if len(ids) == 1: + sql = text(f'delete from `{data_schema.__tablename__}` where id = "{ids[0]}"') + else: + sql = text(f"delete from `{data_schema.__tablename__}` where id in {tuple(ids)}") - session = get_db_session(provider=provider, data_schema=data_schema) - if force_update: - ids = df_current["id"].tolist() - if len(ids) == 1: - sql = text(f'delete from `{data_schema.__tablename__}` where id = "{ids[0]}"') + conn.execute(sql) else: - sql = text(f"delete from `{data_schema.__tablename__}` where id in {tuple(ids)}") - - session.execute(sql) - session.commit() - - else: - current = get_data( - session=session, - data_schema=data_schema, - columns=[data_schema.id], - provider=provider, - ids=df_current["id"].tolist(), - ) - if pd_is_not_null(current): - df_current = df_current[~df_current["id"].isin(current["id"])] - session.commit() - - if pd_is_not_null(df_current): - saved = saved + len(df_current) - df_current.to_sql(data_schema.__tablename__, db_engine, index=False, if_exists="append", dtype=dtype) - + current = get_data( + session=session, + data_schema=data_schema, + columns=[data_schema.id], + provider=provider, + ids=df_current["id"].tolist(), + ) + if pd_is_not_null(current): + df_current = df_current[~df_current["id"].isin(current["id"])] + session.commit() + + if pd_is_not_null(df_current): + saved = saved + len(df_current) + df_current.to_sql(data_schema.__tablename__, conn, index=False, if_exists="append", dtype=dtype) + conn.commit() return saved diff --git a/src/zvt/contract/model.py b/src/zvt/contract/model.py index 7dfaa2a4..3e49204c 100644 --- a/src/zvt/contract/model.py +++ b/src/zvt/contract/model.py @@ -4,9 +4,11 @@ from pydantic import BaseModel, ConfigDict -class MixinModel(BaseModel): - model_config = ConfigDict(from_attributes=True) +class CustomModel(BaseModel): + model_config = ConfigDict(from_attributes=True, allow_inf_nan=True) + +class MixinModel(CustomModel): id: str entity_id: str timestamp: datetime diff --git a/src/zvt/domain/meta/stock_meta.py b/src/zvt/domain/meta/stock_meta.py index c82f59fb..110a83ee 100644 --- a/src/zvt/domain/meta/stock_meta.py +++ b/src/zvt/domain/meta/stock_meta.py @@ -13,16 +13,6 @@ @register_entity(entity_type="stock") class Stock(StockMetaBase, TradableEntity): __tablename__ = "stock" - #: 昨日收盘价 - pre_close = Column(Float) - #: 今日涨停价 - limit_up_price = Column(Float) - # :今日跌停价 - limit_down_price = Column(Float) - #: 流通股本 - float_volume = Column(BigInteger) - #: 总股份 - total_volume = Column(BigInteger) #: 个股详情 diff --git a/src/zvt/domain/misc/__init__.py b/src/zvt/domain/misc/__init__.py index 3589e20b..4da05788 100644 --- a/src/zvt/domain/misc/__init__.py +++ b/src/zvt/domain/misc/__init__.py @@ -12,12 +12,6 @@ __all__ += _overall_all -# import all from submodule stock_events -from .stock_events import * -from .stock_events import __all__ as _stock_events_all - -__all__ += _stock_events_all - # import all from submodule money_flow from .money_flow import * from .money_flow import __all__ as _money_flow_all diff --git a/src/zvt/domain/quotes/stock/stock_quote.py b/src/zvt/domain/quotes/stock/stock_quote.py index f5a0ae96..80b08633 100644 --- a/src/zvt/domain/quotes/stock/stock_quote.py +++ b/src/zvt/domain/quotes/stock/stock_quote.py @@ -5,11 +5,13 @@ from zvt.contract import Mixin from zvt.contract.register import register_schema -StockQuotesBase = declarative_base() +StockQuoteBase = declarative_base() -class StockQuotes(StockQuotesBase, Mixin): - __tablename__ = "stock_quotes" +class StockQuote(StockQuoteBase, Mixin): + __tablename__ = "stock_quote" + code = Column(String(length=32)) + name = Column(String(length=32)) #: UNIX时间戳 time = Column(Integer) @@ -39,7 +41,7 @@ class StockQuotes(StockQuotesBase, Mixin): total_cap = Column(Float) -register_schema(providers=["qmt"], db_name="stock_quotes", schema_base=StockQuotesBase, entity_type="stock") +register_schema(providers=["qmt"], db_name="stock_quote", schema_base=StockQuoteBase, entity_type="stock") # the __all__ is generated -__all__ = ["StockQuotes"] +__all__ = ["StockQuote"] diff --git a/src/zvt/recorders/em/em_api.py b/src/zvt/recorders/em/em_api.py index 6aad3081..8aa79d3a 100644 --- a/src/zvt/recorders/em/em_api.py +++ b/src/zvt/recorders/em/em_api.py @@ -18,12 +18,8 @@ json_callback_param, now_timestamp, to_time_str, - now_pd_timestamp, current_date, - flatten_list, - is_same_date, ) -from zvt.utils.utils import to_str logger = logging.getLogger(__name__) @@ -334,6 +330,90 @@ def get_em_data( raise RuntimeError(f"request em data code: {resp.status_code}, error: {resp.text}") +def get_quotes(): + { + # 市场,2 A股, 3 港股 + "f1": 2, + # 最新价 660/100=6.6 + "f2": 660, + # 涨幅 2000/10000=20% + "f3": 2000, + # 涨跌额 110/100=1.1 + "f4": 110, + # 总手 + "f5": 112596, + # 成交额 + "f6": 74313472.2, + # 换手率 239/10000 + "f8": 239, + # 市盈率 110 + "f9": 11000, + # code + "f12": "300175", + # + "f13": 0, + # name + "f14": "朗源股份", + "f18": 550, + "f19": 80, + "f30": -215, + # 买入价 + "f31": 660, + # 卖出价 + "f32": None, + "f125": 0, + "f139": 5, + "f148": 1, + "f152": 2, + } + { + "f1": 2, + "f2": 1515, + "f3": 1002, + "f4": 138, + "f5": 547165, + "f6": 804705199.0, + "f8": 241, + "f9": 1575, + "f12": "601233", + "f13": 1, + "f14": "桐昆股份", + "f18": 1377, + "f19": 2, + "f30": -1281, + # 买入价 + "f31": 1515, + # 卖出价 + "f32": None, + "f125": 0, + "f139": 2, + "f148": 577, + "f152": 2, + } + { + "f1": 2, + "f2": 611, + "f3": 338, + "f4": 20, + "f5": 478746, + "f6": 293801314.14, + "f8": 803, + "f9": 2067, + "f12": "000788", + "f13": 0, + "f14": "北大医药", + "f18": 591, + "f19": 6, + "f30": -4015, + "f31": 611, + "f32": 612, + "f125": 0, + "f139": 2, + "f148": 1, + "f152": 2, + } + + # quote # url = 'https://push2his.eastmoney.com/api/qt/stock/kline/get?' # 日线 klt=101 @@ -625,48 +705,6 @@ def get_block_stocks(block_id, name=""): return the_list -def get_events(entity_id, session=None, fetch_count=2000): - _, _, code = decode_entity_id(entity_id) - - datas = get_em_data( - session=session, - fields=None, - request_type="RTP_F10_DETAIL", - source="SECURITIES", - params=f"{code}.{get_exchange(code)}", - fetch_all=False, - fetch_count=fetch_count, - ) - if not datas: - return None - datas = flatten_list(datas) - stock_events = [] - checking_date = None - index = 0 - for item in datas: - the_date = item["NOTICE_DATE"] - event_type = item["EVENT_TYPE"] - if checking_date == the_date: - index = index + 1 - the_id = f"{entity_id}_{the_date}_{event_type}_{index}" - else: - checking_date = the_date - index = 0 - the_id = f"{entity_id}_{the_date}_{event_type}" - event = { - "id": the_id, - "entity_id": entity_id, - "timestamp": to_pd_timestamp(the_date), - "event_type": event_type, - "specific_event_type": item["SPECIFIC_EVENTTYPE"], - "notice_date": to_pd_timestamp(the_date), - "level1_content": to_str(item["LEVEL1_CONTENT"]), - "level2_content": to_str(item["LEVEL2_CONTENT"]), - } - stock_events.append(event) - return stock_events - - def get_news(entity_id, ps=200, index=1, start_timestamp=None): sec_id = to_em_sec_id(entity_id=entity_id) url = f"https://np-listapi.eastmoney.com/comm/wap/getListInfo?cb=callback&client=wap&type=1&mTypeAndCode={sec_id}&pageSize={ps}&pageIndex={index}&callback=jQuery1830017478247906740352_{now_timestamp() - 1}&_={now_timestamp()}" @@ -890,7 +928,6 @@ def to_zvt_code(code): "get_future_list", "get_tradable_list", "get_block_stocks", - "get_events", "get_news", "to_em_fc", "to_em_entity_flag", diff --git a/src/zvt/recorders/em/misc/__init__.py b/src/zvt/recorders/em/misc/__init__.py index fa240ff0..be419f46 100644 --- a/src/zvt/recorders/em/misc/__init__.py +++ b/src/zvt/recorders/em/misc/__init__.py @@ -11,9 +11,3 @@ from .em_stock_news_recorder import __all__ as _em_stock_news_recorder_all __all__ += _em_stock_news_recorder_all - -# import all from submodule em_stock_events_recorder -from .em_stock_events_recorder import * -from .em_stock_events_recorder import __all__ as _em_stock_events_recorder_all - -__all__ += _em_stock_events_recorder_all diff --git a/src/zvt/recorders/qmt/quotes/qmt_kdata_recorder.py b/src/zvt/recorders/qmt/quotes/qmt_kdata_recorder.py index 02b5ed52..12c2dc19 100644 --- a/src/zvt/recorders/qmt/quotes/qmt_kdata_recorder.py +++ b/src/zvt/recorders/qmt/quotes/qmt_kdata_recorder.py @@ -2,7 +2,7 @@ import pandas as pd from zvt.api.kdata import get_kdata_schema -from zvt.broker.qmt import qmt_api +from zvt.broker.qmt import qmt_quote from zvt.contract import IntervalLevel, AdjustType from zvt.contract.api import df_to_db from zvt.contract.recorder import FixedCycleDataRecorder @@ -73,7 +73,7 @@ def record(self, entity, start, end, size, timestamps): start = "2005-01-01" if not end: end = current_date() - df = qmt_api.get_kdata( + df = qmt_quote.get_kdata( entity_id=entity.id, start_timestamp=start, end_timestamp=end, diff --git a/src/zvt/rest/trading.py b/src/zvt/rest/trading.py index d72eea50..a4226037 100644 --- a/src/zvt/rest/trading.py +++ b/src/zvt/rest/trading.py @@ -4,7 +4,17 @@ from fastapi_pagination import Page import zvt.trading.trading_service as trading_service -from zvt.trading.trading_models import BuildTradingPlanModel, TradingPlanModel, QueryTradingPlanModel +from zvt.common.trading_models import BuyPositionStrategy, SellPositionStrategy + +# from zvt.broker.qmt.context import qmt_context +from zvt.trading.trading_models import ( + BuildTradingPlanModel, + TradingPlanModel, + QueryTradingPlanModel, + QueryStockQuoteModel, + StockQuoteModel, + StockQuoteStatsModel, +) trading_router = APIRouter( prefix="/api/trading", @@ -13,6 +23,11 @@ ) +@trading_router.post("/query_stock_quotes", response_model=StockQuoteStatsModel) +def query_trading_plan(query_stock_quote_model: QueryStockQuoteModel): + return trading_service.get_stock_quotes(query_stock_quote_model) + + @trading_router.post("/build_trading_plan", response_model=TradingPlanModel) def build_trading_plan(build_trading_plan_model: BuildTradingPlanModel): return trading_service.build_trading_plan(build_trading_plan_model) @@ -31,3 +46,13 @@ def get_current_trading_plan(): @trading_router.get("/get_future_trading_plan", response_model=List[TradingPlanModel]) def get_future_trading_plan(): return trading_service.get_future_trading_plan() + + +# @trading_router.post("/buy", response_model=List[TradingPlanModel]) +# def get_future_trading_plan(buy_position_strategy: BuyPositionStrategy): +# return qmt_context.qmt_account.buy(buy_position_strategy) +# +# +# @trading_router.post("/sell", response_model=List[TradingPlanModel]) +# def get_future_trading_plan(sell_position_strategy: SellPositionStrategy): +# return qmt_context.qmt_account.sell(sell_position_strategy) diff --git a/src/zvt/tag/tag_service.py b/src/zvt/tag/tag_service.py index 1edc250c..35cf8e5f 100644 --- a/src/zvt/tag/tag_service.py +++ b/src/zvt/tag/tag_service.py @@ -3,13 +3,11 @@ from typing import List import pandas as pd -import sqlalchemy from sqlalchemy import func import zvt.contract.api as contract_api -from zvt.api import get_trade_dates from zvt.api.selector import get_entity_ids_by_filter -from zvt.domain import BlockStock, Block, Stock, StockEvents +from zvt.domain import BlockStock, Block, Stock from zvt.tag.tag_models import ( SetStockTagsModel, CreateStockPoolInfoModel, @@ -30,9 +28,6 @@ to_pd_timestamp, compare_dicts, flatten_list, - date_time_by_interval, - TIME_FORMAT_DAY, - current_date, ) from zvt.utils.utils import fill_dict @@ -194,63 +189,53 @@ def build_sub_tags(self): logger.info(f"keep current tags set by user for: {entity_id}") keep_current = True - start_timestamp = max(current_stock_tags.timestamp, to_pd_timestamp("2005-01-01")) current_sub_tag = current_stock_tags.sub_tag - filters = [StockEvents.event_type == "新增概念", StockEvents.entity_id == entity_id] + filters = [BlockStock.stock_id == entity_id] if current_sub_tag: logger.info(f"{entity_id} current_sub_tag: {current_sub_tag}") current_sub_tags = current_stock_tags.sub_tags.keys() - for current_sub_tag in current_sub_tags: - filters = filters + [sqlalchemy.not_(StockEvents.level1_content.contains(current_sub_tag))] + filters = filters + [BlockStock.name.notin_(current_sub_tags)] - logger.info(f"get stock_events from start_timestamp: {start_timestamp}") + df_block = Block.query_data(provider="em", filters=[Block.category == "concept"]) + concept_codes = df_block["code"].tolist() + filters = filters + [BlockStock.code.in_(concept_codes)] - stock_events: List[StockEvents] = StockEvents.query_data( + block_stocks: List[BlockStock] = BlockStock.query_data( provider="em", - start_timestamp=start_timestamp, filters=filters, - order=StockEvents.timestamp.asc(), return_type="domain", ) - if not stock_events: - logger.info(f"no stock_events for: {entity_id}") - - for stock_event in stock_events: - event_timestamp = to_pd_timestamp(stock_event.timestamp) - if stock_event.level1_content: - contents = stock_event.level1_content.split(":") - if len(contents) < 2: - logger.warning(f"wrong stock_event:{stock_event.level1_content}") - else: - sub_tag = contents[1] - if sub_tag in get_sub_tags(): - if stock_event.level2_content: - sub_tag_reason = stock_event.level2_content.split(":")[1] - else: - sub_tag_reason = f"来自概念:{sub_tag}" - - main_tag = get_concept_main_tag_mapping().get(sub_tag) - main_tag_reason = sub_tag_reason - if main_tag == "其他": - main_tag = current_stock_tags.main_tag - main_tag_reason = current_stock_tags.main_tag_reason - - set_stock_tags_model = SetStockTagsModel( - entity_id=entity_id, - main_tag=main_tag, - main_tag_reason=main_tag_reason, - sub_tag=sub_tag, - sub_tag_reason=sub_tag_reason, - active_hidden_tags=None, - ) - build_stock_tags( - set_stock_tags_model=set_stock_tags_model, - timestamp=event_timestamp, - set_by_user=False, - keep_current=keep_current, - ) - else: - logger.info(f"ignore {sub_tag} not in sub_tag_info yet") + if not block_stocks: + logger.info(f"no block_stocks for: {entity_id}") + + for block_stock in block_stocks: + sub_tag = block_stock.name + if sub_tag in get_sub_tags(): + sub_tag_reason = f"来自概念:{sub_tag}" + + main_tag = get_concept_main_tag_mapping().get(sub_tag) + main_tag_reason = sub_tag_reason + if main_tag == "其他": + main_tag = current_stock_tags.main_tag + main_tag_reason = current_stock_tags.main_tag_reason + + set_stock_tags_model = SetStockTagsModel( + entity_id=entity_id, + main_tag=main_tag, + main_tag_reason=main_tag_reason, + sub_tag=sub_tag, + sub_tag_reason=sub_tag_reason, + active_hidden_tags=None, + ) + print(set_stock_tags_model) + build_stock_tags( + set_stock_tags_model=set_stock_tags_model, + timestamp=block_stock.timestamp, + set_by_user=False, + keep_current=keep_current, + ) + else: + logger.info(f"ignore {sub_tag} not in sub_tag_info yet") def tag(self): self.build_stock_main_tag() @@ -440,91 +425,9 @@ def activate_sub_tags(activate_sub_tags_model: ActivateSubTagsModel): return result -def build_system_tags(): - entity_ids = get_entity_ids_by_filter(provider="em", ignore_delist=True, ignore_st=False, ignore_new_stock=False) - - latest = StockSystemTags.query_data(limit=1, order=StockSystemTags.timestamp.desc(), return_type="domain") - if latest: - start = date_time_by_interval(to_time_str(latest[0].timestamp, fmt=TIME_FORMAT_DAY)) - else: - start = "2018-01-01" - trade_days = get_trade_dates(start=start, end=current_date()) - - entity_df = Stock.query_data( - provider="em", entity_ids=entity_ids, columns=["entity_id", "code", "name"], index="entity_id" - ) - entity_df = entity_df[["entity_id", "code", "name"]] - - for target_date in trade_days: - logger.info(f"build_system_tags to: {target_date}") - events_list = ["增减持计划", "增发", "配股", "业绩预告", "股票回购", "股东增减持", "诉讼仲裁", "违规处罚", "高管及关联方增减持", "龙虎榜"] - recent_30_days = date_time_by_interval(target_date, -30) - future_30_days = date_time_by_interval(target_date, 30) - df1 = StockEvents.query_data( - entity_ids=entity_ids, - start_timestamp=recent_30_days, - end_timestamp=target_date, - filters=[StockEvents.event_type.in_(events_list)], - ) - - forecast_events_list = [ - "限售解禁", - ] - df2 = StockEvents.query_data( - entity_ids=entity_ids, - start_timestamp=recent_30_days, - end_timestamp=future_30_days, - filters=[StockEvents.event_type.in_(forecast_events_list)], - ) - df = pd.concat([df1, df2]) - df["recent_reduction"] = ((df["event_type"] == "增减持计划") & df["level1_content"].str.contains("减持")) | ( - df["specific_event_type"] == "股东减持" - ) - df["recent_acquisition"] = ( - ((df["event_type"] == "增减持计划") & df["level1_content"].str.contains("增持")) - | (df["specific_event_type"] == "股东增持") - | (df["event_type"] == "股票回购") - ) - - # 解禁 - df["recent_unlock"] = df["event_type"] == "限售解禁" - # 增发配股 - df["recent_additional_or_rights_issue"] = (df["event_type"] == "增发") | (df["event_type"] == "配股") - - # 违规 - df["recent_violation_alert"] = df["event_type"] == "违规处罚" - - # 业绩向好 - df["recent_positive_earnings_news"] = (df["event_type"] == "业绩预告") & ( - df["level1_content"].str.contains("预增|略增|扭亏", regex=True) - ) - # 业绩不行 - df["recent_negative_earnings_news"] = (df["event_type"] == "业绩预告") & ( - df["level1_content"].str.contains("预减|略减|续亏|首亏", regex=True) - ) - - grouped_df = df.groupby("entity_id").agg( - recent_reduction=("recent_reduction", "any"), - recent_acquisition=("recent_acquisition", "any"), - recent_unlock=("recent_unlock", "any"), - recent_additional_or_rights_issue=("recent_additional_or_rights_issue", "any"), - recent_violation_alert=("recent_violation_alert", "any"), - recent_positive_earnings_news=("recent_positive_earnings_news", "any"), - recent_negative_earnings_news=("recent_negative_earnings_news", "any"), - recent_dragon_and_tiger_count=( - "event_type", - lambda x: (x == "龙虎榜").astype(int).sum(), - ), - ) - grouped_df["timestamp"] = to_pd_timestamp(target_date) - result_df = pd.concat([grouped_df, entity_df[entity_df.index.isin(grouped_df.index)]], axis=1) - result_df["id"] = result_df["entity_id"] + "_" + to_time_str(target_date, fmt=TIME_FORMAT_DAY) - contract_api.df_to_db(result_df, data_schema=StockSystemTags, force_update=False, provider="zvt") - - if __name__ == "__main__": # refresh_all_main_tag_by_sub_tag() - activate_sub_tags(ActivateSubTagsModel(sub_tags=["小金属概念"])) + activate_sub_tags(ActivateSubTagsModel(sub_tags=["新型城镇化"])) # build_initial_main_tag_info() # build_initial_sub_tag_info() # build_initial_hidden_tag_info() diff --git a/src/zvt/trading/trading_models.py b/src/zvt/trading/trading_models.py index 8925595b..92837649 100644 --- a/src/zvt/trading/trading_models.py +++ b/src/zvt/trading/trading_models.py @@ -5,9 +5,9 @@ from pydantic import BaseModel, field_validator, Field from pydantic_core.core_schema import ValidationInfo -from zvt.common.query_models import TimeRange +from zvt.common.query_models import TimeRange, OrderByType from zvt.contract import IntervalLevel, AdjustType -from zvt.contract.model import MixinModel +from zvt.contract.model import MixinModel, CustomModel from zvt.tag.common import StockPoolType from zvt.tag.tag_utils import get_main_tags, get_sub_tags, get_hidden_tags, get_stock_pool_names from zvt.trader import TradingSignalType @@ -15,6 +15,64 @@ from zvt.utils import date_time_by_interval, current_date, tomorrow_date, to_pd_timestamp +class QueryStockQuoteModel(CustomModel): + + main_tag: Optional[str] = Field(default=None) + entity_ids: Optional[List[str]] = Field(default=None) + limit: int = Field(default=50) + order_by_type: Optional[OrderByType] = Field(default=OrderByType.desc) + order_by_field: Optional[str] = Field(default="change_pct") + + +class StockQuoteModel(MixinModel): + #: 代码 + code: str + #: 名字 + name: str + + #: UNIX时间戳 + time: int + #: 最新价 + price: float + # 涨跌幅 + change_pct: float + # 成交金额 + turnover: float + # 换手率 + turnover_rate: float + #: 是否涨停 + is_limit_up: bool + #: 封涨停金额 + limit_up_amount: Optional[float] = Field(default=None) + #: 是否跌停 + is_limit_down: bool + #: 封跌停金额 + limit_down_amount: Optional[float] = Field(default=None) + #: 5挡卖单金额 + ask_amount: float + #: 5挡买单金额 + bid_amount: float + #: 流通市值 + float_cap: float + #: 总市值 + total_cap: float + + main_tag: str = Field(default=None) + sub_tag: Union[str, None] = Field(default=None) + hidden_tags: Union[List[str], None] = Field(default=None) + + +class StockQuoteStatsModel(CustomModel): + limit_up_count: int + limit_down_count: int + up_count: int + down_count: int + change_pct: float + turnover: float + + quotes: List[StockQuoteModel] + + class TradingPlanModel(MixinModel): stock_id: str stock_code: str diff --git a/src/zvt/trading/trading_service.py b/src/zvt/trading/trading_service.py index 346439ee..dde312c2 100644 --- a/src/zvt/trading/trading_service.py +++ b/src/zvt/trading/trading_service.py @@ -1,16 +1,25 @@ # -*- coding: utf-8 -*- +import json import logging from typing import List, Union +import numpy as np import pandas as pd from fastapi_pagination.ext.sqlalchemy import paginate import zvt.contract.api as contract_api from zvt.contract import IntervalLevel, AdjustType -from zvt.domain import Stock +from zvt.domain import Stock, StockQuote +from zvt.tag import StockTags from zvt.trader import StockTrader from zvt.trading.common import ExecutionStatus -from zvt.trading.trading_models import BuildTradingPlanModel, QueryTradingPlanModel +from zvt.trading.trading_models import ( + BuildTradingPlanModel, + QueryTradingPlanModel, + QueryStockQuoteModel, + StockQuoteStatsModel, + StockQuoteModel, +) from zvt.trading.trading_schemas import TradingPlan from zvt.utils import to_time_str, to_pd_timestamp, now_pd_timestamp, date_time_by_interval, current_date @@ -102,3 +111,64 @@ def check_trading_plan(): ) logger.info(f"current plans:{plans}") + + +def get_stock_quotes(query_stock_quote_model: QueryStockQuoteModel): + if query_stock_quote_model.main_tag: + tags_dict = StockTags.query_data( + filters=[StockTags.main_tag == query_stock_quote_model.main_tag], + return_type="dict", + ) + if not tags_dict: + return [] + entity_ids = [item["entity_id"] for item in tags_dict] + else: + tags_dict = StockTags.query_data( + return_type="dict", + ) + entity_ids = query_stock_quote_model.entity_ids + entity_tags_map = {item["entity_id"]: item for item in tags_dict} + + order = eval(f"StockQuote.{query_stock_quote_model.order_by_field}.{query_stock_quote_model.order_by_type.value}()") + + quotes = StockQuote.query_data( + order=order, entity_ids=entity_ids, limit=query_stock_quote_model.limit, return_type="dict" + ) + + df = pd.DataFrame.from_records(data=quotes) + + def set_tags(quote): + entity_id = quote["entity_id"] + main_tag = entity_tags_map.get(entity_id, {}).get("main_tag", None) + sub_tag = entity_tags_map.get(entity_id, {}).get("sub_tag", None) + return pd.Series({"main_tag": main_tag, "sub_tag": sub_tag}) + + df[["main_tag", "sub_tag"]] = df.apply(set_tags, axis=1) + + up_count = (df["change_pct"] > 0).sum() + down_count = (df["change_pct"] < 0).sum() + turnover = df["turnover"].sum() + change_pct = df["change_pct"].mean() + limit_up_count = df["is_limit_up"].sum() + limit_down_count = df["is_limit_down"].sum() + + result = { + "up_count": up_count, + "down_count": down_count, + "turnover": turnover, + "change_pct": change_pct, + "limit_up_count": limit_up_count, + "limit_down_count": limit_down_count, + "quotes": quotes, + } + print(result) + + return result + + +def buy_stocks(): + pass + + +def sell_stocks(): + pass diff --git a/src/zvt/xtquant.py b/src/zvt/xtquant.py deleted file mode 100644 index b7c52852..00000000 --- a/src/zvt/xtquant.py +++ /dev/null @@ -1,171 +0,0 @@ -# coding=utf-8 -from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback -from xtquant.xttype import StockAccount -from xtquant import xtconstant - - -class MyXtQuantTraderCallback(XtQuantTraderCallback): - def on_disconnected(self): - """ - 连接断开 - :return: - """ - print("connection lost") - - def on_stock_order(self, order): - """ - 委托回报推送 - :param order: XtOrder对象 - :return: - """ - print("on order callback:") - print(order.stock_code, order.order_status, order.order_sysid) - - def on_stock_asset(self, asset): - """ - 资金变动推送 - :param asset: XtAsset对象 - :return: - """ - print("on asset callback") - print(asset.account_id, asset.cash, asset.total_asset) - - def on_stock_trade(self, trade): - """ - 成交变动推送 - :param trade: XtTrade对象 - :return: - """ - print("on trade callback") - print(trade.account_id, trade.stock_code, trade.order_id) - - def on_stock_position(self, position): - """ - 持仓变动推送 - :param position: XtPosition对象 - :return: - """ - print("on position callback") - print(position.stock_code, position.volume) - - def on_order_error(self, order_error): - """ - 委托失败推送 - :param order_error:XtOrderError 对象 - :return: - """ - print("on order_error callback") - print(order_error.order_id, order_error.error_id, order_error.error_msg) - - def on_cancel_error(self, cancel_error): - """ - 撤单失败推送 - :param cancel_error: XtCancelError 对象 - :return: - """ - print("on cancel_error callback") - print(cancel_error.order_id, cancel_error.error_id, cancel_error.error_msg) - - def on_order_stock_async_response(self, response): - """ - 异步下单回报推送 - :param response: XtOrderResponse 对象 - :return: - """ - print("on_order_stock_async_response") - print(response.account_id, response.order_id, response.seq) - - def on_account_status(self, status): - """ - :param response: XtAccountStatus 对象 - :return: - """ - print("on_account_status") - print(status.account_id, status.account_type, status.status) - - -if __name__ == "__main__": - print("demo test") - # path为mini qmt客户端安装目录下userdata_mini路径 - path = "D:\\迅投极速交易终端 睿智融科版\\userdata_mini" - # session_id为会话编号,策略使用方对于不同的Python策略需要使用不同的会话编号 - session_id = 123456 - xt_trader = XtQuantTrader(path, session_id) - # 创建资金账号为1000000365的证券账号对象 - acc = StockAccount("1000000365") - # StockAccount可以用第二个参数指定账号类型,如沪港通传'HUGANGTONG',深港通传'SHENGANGTONG' - # acc = StockAccount('1000000365','STOCK') - # 创建交易回调类对象,并声明接收回调 - callback = MyXtQuantTraderCallback() - xt_trader.register_callback(callback) - # 启动交易线程 - xt_trader.start() - # 建立交易连接,返回0表示连接成功 - connect_result = xt_trader.connect() - if connect_result != 0: - import sys - - sys.exit("链接失败,程序即将退出 %d" % connect_result) - # 对交易回调进行订阅,订阅后可以收到交易主推,返回0表示订阅成功 - subscribe_result = xt_trader.subscribe(acc) - if subscribe_result != 0: - print("账号订阅失败 %d" % subscribe_result) - print(subscribe_result) - stock_code = "600000.SH" - # 使用指定价下单,接口返回订单编号,后续可以用于撤单操作以及查询委托状态 - print("order using the fix price:") - fix_result_order_id = xt_trader.order_stock( - acc, stock_code, xtconstant.STOCK_BUY, 200, xtconstant.FIX_PRICE, 10.5, "strategy_name", "remark" - ) - print(fix_result_order_id) - # 使用订单编号撤单 - print("cancel order:") - cancel_order_result = xt_trader.cancel_order_stock(acc, fix_result_order_id) - print(cancel_order_result) - # 使用异步下单接口,接口返回下单请求序号seq,seq可以和on_order_stock_async_response的委托反馈response对应起来 - print("order using async api:") - async_seq = xt_trader.order_stock( - acc, stock_code, xtconstant.STOCK_BUY, 200, xtconstant.FIX_PRICE, 10.5, "strategy_name", "remark" - ) - print(async_seq) - # 查询证券资产 - print("query asset:") - asset = xt_trader.query_stock_asset(acc) - if asset: - print("asset:") - print("cash {0}".format(asset.cash)) - # 根据订单编号查询委托 - print("query order:") - order = xt_trader.query_stock_order(acc, fix_result_order_id) - if order: - print("order:") - print("order {0}".format(order.order_id)) - # 查询当日所有的委托 - print("query orders:") - orders = xt_trader.query_stock_orders(acc) - print("orders:", len(orders)) - if len(orders) != 0: - print("last order:") - print("{0} {1} {2}".format(orders[-1].stock_code, orders[-1].order_volume, orders[-1].price)) - # 查询当日所有的成交 - print("query trade:") - trades = xt_trader.query_stock_trades(acc) - print("trades:", len(trades)) - if len(trades) != 0: - print("last trade:") - print("{0} {1} {2}".format(trades[-1].stock_code, trades[-1].traded_volume, trades[-1].traded_price)) - # 查询当日所有的持仓 - print("query positions:") - positions = xt_trader.query_stock_positions(acc) - print("positions:", len(positions)) - if len(positions) != 0: - print("last position:") - print("{0} {1} {2}".format(positions[-1].account_id, positions[-1].stock_code, positions[-1].volume)) - # 根据股票代码查询对应持仓 - print("query position:") - position = xt_trader.query_stock_position(acc, stock_code) - if position: - print("position:") - print("{0} {1} {2}".format(position.account_id, position.stock_code, position.volume)) - # 阻塞线程,接收交易推送 - xt_trader.run_forever() diff --git a/src/zvt_vip/__init__.py b/src/zvt_vip/__init__.py new file mode 100644 index 00000000..29e9de99 --- /dev/null +++ b/src/zvt_vip/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import zvt_vip.recorders as zvt_vip_recorders diff --git a/src/zvt_vip/dataset/__init__.py b/src/zvt_vip/dataset/__init__.py new file mode 100644 index 00000000..8c5e6183 --- /dev/null +++ b/src/zvt_vip/dataset/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# the __all__ is generated +__all__ = [] + +# __init__.py structure: +# common code of the package +# export interface in __all__ which contains __all__ of its sub modules + +# import all from submodule stock_events +from .stock_events import * +from .stock_events import __all__ as _stock_events_all + +__all__ += _stock_events_all diff --git a/src/zvt/domain/misc/stock_events.py b/src/zvt_vip/dataset/stock_events.py similarity index 100% rename from src/zvt/domain/misc/stock_events.py rename to src/zvt_vip/dataset/stock_events.py diff --git a/src/zvt_vip/recorders/__init__.py b/src/zvt_vip/recorders/__init__.py new file mode 100644 index 00000000..25683d86 --- /dev/null +++ b/src/zvt_vip/recorders/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# the __all__ is generated +__all__ = [] + +# __init__.py structure: +# common code of the package +# export interface in __all__ which contains __all__ of its sub modules + +# import all from submodule em_api +from .em_api import * +from .em_api import __all__ as _em_api_all + +__all__ += _em_api_all + +# import all from submodule em_stock_events_recorder +from .em_stock_events_recorder import * +from .em_stock_events_recorder import __all__ as _em_stock_events_recorder_all + +__all__ += _em_stock_events_recorder_all diff --git a/src/zvt_vip/recorders/em_api.py b/src/zvt_vip/recorders/em_api.py new file mode 100644 index 00000000..67fe6f3b --- /dev/null +++ b/src/zvt_vip/recorders/em_api.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +import logging + +from zvt.contract.api import decode_entity_id +from zvt.recorders.em import em_api +from zvt.utils import ( + to_pd_timestamp, + flatten_list, +) +from zvt.utils.utils import to_str + +logger = logging.getLogger(__name__) + + +def get_events(entity_id, session=None, fetch_count=2000): + _, _, code = decode_entity_id(entity_id) + + datas = em_api.get_em_data( + session=session, + fields=None, + request_type="RTP_F10_DETAIL", + source="SECURITIES", + params=f"{code}.{em_api.get_exchange(code)}", + fetch_all=False, + fetch_count=fetch_count, + ) + if not datas: + return None + datas = flatten_list(datas) + stock_events = [] + checking_date = None + index = 0 + for item in datas: + the_date = item["NOTICE_DATE"] + event_type = item["EVENT_TYPE"] + if checking_date == the_date: + index = index + 1 + the_id = f"{entity_id}_{the_date}_{event_type}_{index}" + else: + checking_date = the_date + index = 0 + the_id = f"{entity_id}_{the_date}_{event_type}" + event = { + "id": the_id, + "entity_id": entity_id, + "timestamp": to_pd_timestamp(the_date), + "event_type": event_type, + "specific_event_type": item["SPECIFIC_EVENTTYPE"], + "notice_date": to_pd_timestamp(the_date), + "level1_content": to_str(item["LEVEL1_CONTENT"]), + "level2_content": to_str(item["LEVEL2_CONTENT"]), + } + stock_events.append(event) + return stock_events + + +# the __all__ is generated +__all__ = ["get_events"] diff --git a/src/zvt/recorders/em/misc/em_stock_events_recorder.py b/src/zvt_vip/recorders/em_stock_events_recorder.py similarity index 89% rename from src/zvt/recorders/em/misc/em_stock_events_recorder.py rename to src/zvt_vip/recorders/em_stock_events_recorder.py index 4f85c25c..600e4b90 100644 --- a/src/zvt/recorders/em/misc/em_stock_events_recorder.py +++ b/src/zvt_vip/recorders/em_stock_events_recorder.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- import pandas as pd +import requests -from zvt.contract.api import df_to_db, get_db_session, get_entities, get_data +from zvt.contract.api import df_to_db from zvt.contract.recorder import FixedCycleDataRecorder from zvt.domain import Stock -from zvt.domain.misc.stock_events import StockEvents -from zvt.recorders.em import em_api -from zvt.utils import to_pd_timestamp, count_interval, now_pd_timestamp, pd_is_not_null, now_time_str - -import requests +from zvt.utils import to_pd_timestamp, count_interval, now_pd_timestamp +from zvt_vip.dataset.stock_events import StockEvents +from zvt_vip.recorders import em_api class EMStockEventsRecorder(FixedCycleDataRecorder): diff --git a/scripts/__init__.py b/src/zvt_vip/scripts/__init__.py similarity index 100% rename from scripts/__init__.py rename to src/zvt_vip/scripts/__init__.py diff --git a/src/zvt_vip/scripts/kdata_runner.py b/src/zvt_vip/scripts/kdata_runner.py new file mode 100644 index 00000000..cc57728a --- /dev/null +++ b/src/zvt_vip/scripts/kdata_runner.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +import logging + +from apscheduler.schedulers.background import BackgroundScheduler + +from zvt import init_log, zvt_config +from zvt.api.selector import get_entity_ids_by_filter +from zvt.domain import ( + Stock, + Stock1dHfqKdata, + Stockhk, + Stockhk1dHfqKdata, + Block, + Block1dKdata, + BlockCategory, + Index, + Index1dKdata, + StockNews, + LimitUpInfo, +) +from zvt.informer import EmailInformer +from zvt.utils import current_date +from zvt.utils.inform_utils import inform_email +from zvt.utils.recorder_utils import run_data_recorder + +logger = logging.getLogger(__name__) + +sched = BackgroundScheduler() + + +@sched.scheduled_job("cron", hour=16, minute=30, day_of_week="mon-fri") +def record_stock_news(data_provider="em"): + normal_stock_ids = get_entity_ids_by_filter( + provider="em", ignore_delist=True, ignore_st=False, ignore_new_stock=False + ) + + run_data_recorder( + entity_ids=normal_stock_ids, + day_data=True, + domain=StockNews, + data_provider=data_provider, + force_update=False, + sleeping_time=2, + ) + + +def report_limit_up(): + latest_data = LimitUpInfo.query_data(order=LimitUpInfo.timestamp.desc(), limit=1, return_type="domain") + timestamp = latest_data[0].timestamp + df = LimitUpInfo.query_data(start_timestamp=timestamp, end_timestamp=timestamp, columns=["code", "name", "reason"]) + df["reason"] = df["reason"].str.split("+") + print(df) + EmailInformer().send_message(zvt_config["email_username"], f"{timestamp} 热门报告", f"{df}") + + +@sched.scheduled_job("cron", hour=15, minute=30, day_of_week="mon-fri") +def record_stock_data(data_provider="em", entity_provider="em", sleeping_time=0): + email_action = EmailInformer() + # 涨停数据 + run_data_recorder(domain=LimitUpInfo, data_provider=None, force_update=False) + report_limit_up() + + # A股指数 + run_data_recorder(domain=Index, data_provider=data_provider, force_update=False) + # A股指数行情 + run_data_recorder( + domain=Index1dKdata, + data_provider=data_provider, + entity_provider=entity_provider, + day_data=True, + sleeping_time=sleeping_time, + ) + + # 板块(概念,行业) + run_data_recorder(domain=Block, entity_provider=entity_provider, data_provider=entity_provider, force_update=False) + # 板块行情(概念,行业) + run_data_recorder( + domain=Block1dKdata, + entity_provider=entity_provider, + data_provider=entity_provider, + day_data=True, + sleeping_time=sleeping_time, + ) + # run_data_recorder( + # domain=BlockStock, + # entity_provider=entity_provider, + # data_provider=entity_provider, + # sleeping_time=sleeping_time, + # ) + + # 报告新概念和行业 + df = Block.query_data( + filters=[Block.category == BlockCategory.concept.value], + order=Block.list_date.desc(), + index="entity_id", + limit=7, + ) + + inform_email( + entity_ids=df.index.tolist(), entity_type="block", target_date=current_date(), title="report 新概念", provider="em" + ) + + # A股标的 + run_data_recorder(domain=Stock, data_provider=data_provider, force_update=False) + # A股后复权行情 + normal_stock_ids = get_entity_ids_by_filter( + provider="em", ignore_delist=True, ignore_st=False, ignore_new_stock=False + ) + + run_data_recorder( + entity_ids=normal_stock_ids, + domain=Stock1dHfqKdata, + data_provider=data_provider, + entity_provider=entity_provider, + day_data=True, + sleeping_time=sleeping_time, + return_unfinished=True, + ) + + +@sched.scheduled_job("cron", hour=16, minute=30, day_of_week="mon-fri") +def record_stockhk_data(data_provider="em", entity_provider="em", sleeping_time=2): + # 港股标的 + run_data_recorder(domain=Stockhk, data_provider=data_provider, force_update=False) + # 港股后复权行情 + df = Stockhk.query_data(filters=[Stockhk.south == True], index="entity_id") + run_data_recorder( + domain=Stockhk1dHfqKdata, + entity_ids=df.index.tolist(), + data_provider=data_provider, + entity_provider=entity_provider, + day_data=True, + sleeping_time=sleeping_time, + ) + + +if __name__ == "__main__": + init_log("kdata_runner.log") + + record_stock_data() + record_stockhk_data() + + sched.start() + + sched._thread.join() diff --git a/scripts/prepare_recent_data.py b/src/zvt_vip/scripts/prepare_recent_data.py similarity index 79% rename from scripts/prepare_recent_data.py rename to src/zvt_vip/scripts/prepare_recent_data.py index 612cff99..99b1bedd 100644 --- a/scripts/prepare_recent_data.py +++ b/src/zvt_vip/scripts/prepare_recent_data.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +from zvt.api.selector import get_entity_ids_by_filter +from zvt.domain import BlockStock from zvt.tag.tag_models import ActivateSubTagsModel from zvt.tag.tag_service import activate_sub_tags from zvt.utils.recorder_utils import run_data_recorder -from zvt.api.selector import get_entity_ids_by_filter -from zvt.domain import StockEvents, BlockStock -from zvt.tag import StockAutoTagger +from zvt_vip.dataset.stock_events import StockEvents +from zvt_vip.tag.tag_service import VIPStockAutoTagger if __name__ == "__main__": data_provider = "em" @@ -33,5 +34,5 @@ return_unfinished=True, ) - StockAutoTagger().tag() - # activate_sub_tags(ActivateSubTagsModel(sub_tags=["低空经济", "跨境支付"])) + VIPStockAutoTagger().tag() + activate_sub_tags(ActivateSubTagsModel(sub_tags=["合成生物"])) diff --git a/scripts/report_stock_by_tag.py b/src/zvt_vip/scripts/report_stock_by_tag.py similarity index 98% rename from scripts/report_stock_by_tag.py rename to src/zvt_vip/scripts/report_stock_by_tag.py index a77f31f8..b130e522 100644 --- a/scripts/report_stock_by_tag.py +++ b/src/zvt_vip/scripts/report_stock_by_tag.py @@ -2,7 +2,6 @@ import logging from collections import Counter from typing import List -from examples.data_runner.kdata_runner import record_stock_data from examples.utils import add_to_eastmoney, clean_groups from zvt import zvt_config from zvt.contract import AdjustType @@ -15,7 +14,7 @@ from zvt.tag.tag_schemas import TagStats from zvt.tag.tag_stats import build_stock_pool_tag_stats, build_system_stock_pools from zvt.utils import to_pd_timestamp - +from zvt_vip.scripts.kdata_runner import record_stock_data logger = logging.getLogger(__name__) diff --git a/src/zvt_vip/tag/__init__.py b/src/zvt_vip/tag/__init__.py new file mode 100644 index 00000000..26882b27 --- /dev/null +++ b/src/zvt_vip/tag/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# the __all__ is generated +__all__ = [] + +# __init__.py structure: +# common code of the package +# export interface in __all__ which contains __all__ of its sub modules + +# import all from submodule tag_service +from .tag_service import * +from .tag_service import __all__ as _tag_service_all + +__all__ += _tag_service_all diff --git a/src/zvt_vip/tag/tag_service.py b/src/zvt_vip/tag/tag_service.py new file mode 100644 index 00000000..63a9dbe5 --- /dev/null +++ b/src/zvt_vip/tag/tag_service.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +import logging +from typing import List + +import pandas as pd +import sqlalchemy + +import zvt.contract.api as contract_api +from zvt.api import get_trade_dates +from zvt.api.selector import get_entity_ids_by_filter +from zvt.domain import Stock +from zvt.tag import build_stock_tags, StockAutoTagger +from zvt.tag.tag_models import ( + SetStockTagsModel, +) +from zvt.tag.tag_schemas import StockTags, StockSystemTags +from zvt.tag.tag_utils import ( + get_sub_tags, + get_concept_main_tag_mapping, +) +from zvt.utils import ( + to_time_str, + to_pd_timestamp, + date_time_by_interval, + TIME_FORMAT_DAY, + current_date, +) +from zvt_vip.dataset.stock_events import StockEvents + +logger = logging.getLogger(__name__) + + +class VIPStockAutoTagger(StockAutoTagger): + def build_sub_tags(self): + for entity_id in self.entity_ids: + logger.info(f"build sub tag for: {entity_id}") + datas = StockTags.query_data(entity_id=entity_id, limit=1, return_type="domain") + assert len(datas) == 1 + + current_stock_tags = datas[0] + keep_current = False + if current_stock_tags.set_by_user: + logger.info(f"keep current tags set by user for: {entity_id}") + keep_current = True + + start_timestamp = max(current_stock_tags.timestamp, to_pd_timestamp("2005-01-01")) + current_sub_tag = current_stock_tags.sub_tag + filters = [StockEvents.event_type == "新增概念", StockEvents.entity_id == entity_id] + if current_sub_tag: + logger.info(f"{entity_id} current_sub_tag: {current_sub_tag}") + current_sub_tags = current_stock_tags.sub_tags.keys() + for current_sub_tag in current_sub_tags: + filters = filters + [sqlalchemy.not_(StockEvents.level1_content.contains(current_sub_tag))] + + logger.info(f"get stock_events from start_timestamp: {start_timestamp}") + + stock_events: List[StockEvents] = StockEvents.query_data( + provider="em", + start_timestamp=start_timestamp, + filters=filters, + order=StockEvents.timestamp.asc(), + return_type="domain", + ) + if not stock_events: + logger.info(f"no stock_events for: {entity_id}") + + for stock_event in stock_events: + event_timestamp = to_pd_timestamp(stock_event.timestamp) + if stock_event.level1_content: + contents = stock_event.level1_content.split(":") + if len(contents) < 2: + logger.warning(f"wrong stock_event:{stock_event.level1_content}") + else: + sub_tag = contents[1] + if sub_tag in get_sub_tags(): + if stock_event.level2_content: + sub_tag_reason = stock_event.level2_content.split(":")[1] + else: + sub_tag_reason = f"来自概念:{sub_tag}" + + main_tag = get_concept_main_tag_mapping().get(sub_tag) + main_tag_reason = sub_tag_reason + if main_tag == "其他": + main_tag = current_stock_tags.main_tag + main_tag_reason = current_stock_tags.main_tag_reason + + set_stock_tags_model = SetStockTagsModel( + entity_id=entity_id, + main_tag=main_tag, + main_tag_reason=main_tag_reason, + sub_tag=sub_tag, + sub_tag_reason=sub_tag_reason, + active_hidden_tags=None, + ) + build_stock_tags( + set_stock_tags_model=set_stock_tags_model, + timestamp=event_timestamp, + set_by_user=False, + keep_current=keep_current, + ) + else: + logger.info(f"ignore {sub_tag} not in sub_tag_info yet") + + +def build_system_tags(): + entity_ids = get_entity_ids_by_filter(provider="em", ignore_delist=True, ignore_st=False, ignore_new_stock=False) + + latest = StockSystemTags.query_data(limit=1, order=StockSystemTags.timestamp.desc(), return_type="domain") + if latest: + start = date_time_by_interval(to_time_str(latest[0].timestamp, fmt=TIME_FORMAT_DAY)) + else: + start = "2018-01-01" + trade_days = get_trade_dates(start=start, end=current_date()) + + entity_df = Stock.query_data( + provider="em", entity_ids=entity_ids, columns=["entity_id", "code", "name"], index="entity_id" + ) + entity_df = entity_df[["entity_id", "code", "name"]] + + for target_date in trade_days: + logger.info(f"build_system_tags to: {target_date}") + events_list = ["增减持计划", "增发", "配股", "业绩预告", "股票回购", "股东增减持", "诉讼仲裁", "违规处罚", "高管及关联方增减持", "龙虎榜"] + recent_30_days = date_time_by_interval(target_date, -30) + future_30_days = date_time_by_interval(target_date, 30) + df1 = StockEvents.query_data( + entity_ids=entity_ids, + start_timestamp=recent_30_days, + end_timestamp=target_date, + filters=[StockEvents.event_type.in_(events_list)], + ) + + forecast_events_list = [ + "限售解禁", + ] + df2 = StockEvents.query_data( + entity_ids=entity_ids, + start_timestamp=recent_30_days, + end_timestamp=future_30_days, + filters=[StockEvents.event_type.in_(forecast_events_list)], + ) + df = pd.concat([df1, df2]) + df["recent_reduction"] = ((df["event_type"] == "增减持计划") & df["level1_content"].str.contains("减持")) | ( + df["specific_event_type"] == "股东减持" + ) + df["recent_acquisition"] = ( + ((df["event_type"] == "增减持计划") & df["level1_content"].str.contains("增持")) + | (df["specific_event_type"] == "股东增持") + | (df["event_type"] == "股票回购") + ) + + # 解禁 + df["recent_unlock"] = df["event_type"] == "限售解禁" + # 增发配股 + df["recent_additional_or_rights_issue"] = (df["event_type"] == "增发") | (df["event_type"] == "配股") + + # 违规 + df["recent_violation_alert"] = df["event_type"] == "违规处罚" + + # 业绩向好 + df["recent_positive_earnings_news"] = (df["event_type"] == "业绩预告") & ( + df["level1_content"].str.contains("预增|略增|扭亏", regex=True) + ) + # 业绩不行 + df["recent_negative_earnings_news"] = (df["event_type"] == "业绩预告") & ( + df["level1_content"].str.contains("预减|略减|续亏|首亏", regex=True) + ) + + grouped_df = df.groupby("entity_id").agg( + recent_reduction=("recent_reduction", "any"), + recent_acquisition=("recent_acquisition", "any"), + recent_unlock=("recent_unlock", "any"), + recent_additional_or_rights_issue=("recent_additional_or_rights_issue", "any"), + recent_violation_alert=("recent_violation_alert", "any"), + recent_positive_earnings_news=("recent_positive_earnings_news", "any"), + recent_negative_earnings_news=("recent_negative_earnings_news", "any"), + recent_dragon_and_tiger_count=( + "event_type", + lambda x: (x == "龙虎榜").astype(int).sum(), + ), + ) + grouped_df["timestamp"] = to_pd_timestamp(target_date) + result_df = pd.concat([grouped_df, entity_df[entity_df.index.isin(grouped_df.index)]], axis=1) + result_df["id"] = result_df["entity_id"] + "_" + to_time_str(target_date, fmt=TIME_FORMAT_DAY) + contract_api.df_to_db(result_df, data_schema=StockSystemTags, force_update=False, provider="zvt") + + +if __name__ == "__main__": + VIPStockAutoTagger().tag() + build_system_tags() +# the __all__ is generated +__all__ = ["VIPStockAutoTagger", "build_system_tags"]