├── .gitignore
├── .idea
├── .gitignore
├── UniversalRotation.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── LICENSE
├── README.md
├── UniversalRotation.py
├── UniversalRotation.xlsm
└── res
├── LSJZ.png
├── author.png
├── download.png
└── premium_rate.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | log*
132 | ~$UniversalRotation.xlsm
133 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | # /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/UniversalRotation.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {
34 | "associatedIndex": 5
35 | }
36 |
37 |
38 |
39 |
40 |
41 |
42 | {
43 | "keyToString": {
44 | "Python.UniversalRotation.executor": "Run",
45 | "RunOnceActivity.OpenProjectViewOnStart": "true",
46 | "RunOnceActivity.ShowReadmeOnStart": "true",
47 | "RunOnceActivity.git.unshallow": "true",
48 | "git-widget-placeholder": "<unknown>",
49 | "last_opened_file_path": "C:/Users/Administrator/Desktop/wgb/UniversalRotation/res",
50 | "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
51 | }
52 | }
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | 1652152425490
95 |
96 |
97 | 1652152425490
98 |
99 |
100 |
101 |
102 |
111 |
112 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 原贴地址:https://www.jisilu.cn/question/457389
2 |
3 | 下载源码的方式:点击GitHub网页上的Code按钮,选择自己喜欢的下载方式即可:
4 | 
5 |
6 | ### 联系作者
7 | 如果想讨论该tool的用法或者新需求,以及量化轮动策略等相关的内容,可以关注公众号“多策略再平衡小杠杆对冲”(wangbaobaoyzy),共同学习和进步!
8 |
9 | 
10 |
11 | ### 使用前准备
12 | 1. 安装Python3:https://www.python.org/ftp/python/3.8.7/python-3.8.7-amd64.exe
13 | 2. 打开cmd窗口输入:pip install schedule xlwings pandas requests pysnowball browser-cookie3 chinese_calendar
14 | 3. 启用Microsoft Office(不要使用WPS)的Excel中的xlwings宏:
15 | (1)、命令行安装加载项:xlwings addin install。
16 | (2)、在excel中启用加载项: 文件>选项>信任中心>信任中心设置>宏设置 中,选择“启用所有并勾选”并勾选“对VBA对象模型的信任访问”。
17 | 4. 设置Firefox(强烈推荐)或Chrome(我用的版本是108.0.5359.125,建议使用最新版,因为太多网友因为版本太低导致无法使用browser-cookie3)或Opera或Edge或Chromium为默认浏览器,使用他们的任意一个访问任意一个雪球网站以便获取token(不需要登录雪球账号)。
18 |
19 | 注1:如遇到如下报错就代表token过期了:
20 | Exception: b'{"error_description":"\xe9\x81\x87","error_uri":"/v5/stock/quote.json","error_data":null,"error_code":"400016"}'。
21 | 由于我们在Python调用雪球API前需要设置xq_a_token,但它大约只有20天的有效期,之前都是使用F12各种查找xq_a_token然后复制粘贴到我们的Python程序,
22 | 比较繁琐,所以现在改用browser-cookie3工具库来自动化获取电脑浏览器已缓存的cookies(当然啦,浏览器的cookies肯定也会过期,我们只需要坚持每20天左右使用电脑浏览器访问任意一个雪球网站即可刷新电脑浏览器的cookies)。
23 | 但是我发现网友经常遇到Chrome浏览器无法使用browser-cookie3的问题,是因为Chrome浏览器的版本太低导致的。所以建议大家使用Firefox为默认浏览器或者更新到最新版Chrome浏览器。
24 |
25 | 注2:如遇到如下报错就代表chinese_calendar过期了:
26 | no available data for year 2023, only year between [2004, 2022] supported.
27 | 需要升级:pip install -U chinesecalendar
28 |
29 | 注3:作为一个Android程序员,从2022.2月份开始边学边练第一次写Python项目进行量化投资,语法格式肯定不完美,勿喷,仅仅是为了解放调仓的苦恼而写的小玩意,分享出来仅用于学习研究,不可用于商业用途!
30 |
31 | ### (一)、使用Python+Excel可视化你的持仓和最新排名的差异,解决买什么卖什么的烦恼
32 | 1. 这里以"低溢价可转债策略"( https://xueqiu.com/P/ZH3128720 )、"双低可转债策略"( https://xueqiu.com/P/ZH2982722 )为例子,我做了2个轮动的sheet表格:《低溢价可转债轮动》和《双低可转债轮动》。
33 | 2. 点击《可转债实时数据》里面的“点击更新”按钮。
34 | 3. 分别点击《低溢价可转债轮动》和《双低可转债轮动》里面的“点击更新”按钮,如果有其他任何自定义排名也可以手动更新”最新多因子1/2可转债排名“。
35 | 4. 从券商PC端软件导出最新的低溢价可转债持仓和双低可转债持仓的Excel表格,并分别更新到“我的低溢价可转债持仓”、“我的双低可转债持仓”,这时候就可以看到需要轮动的结果了。
36 |
37 | 注3:这里依赖的是Excel自带的各种公式,忽然发现Excel解放手脚的强大了吧?神不神奇惊不惊喜,意不意外?
38 |
39 | 注4:每个sheet页我都做了双排名(即自定义的”最新多因子1/2可转债排名“),满足用户自定义的需求。
40 |
41 | 注5:这里的2个sheet页仅是2个可转债的例子,填充了持仓和最新排名。用户可以将它应用于任何的“依据各种因子进行排名的量化轮动策略”。
42 | 大家可以将这两个sheet作为壳子,替换成任何你们自己的轮动品种,比如小市值低价股票策略、低估股票策略、A/H轮动等等。
43 |
44 |
45 | ### (二)、关于《20天净值增长率和溢价率轮动LOF、ETF和封基》和《20天净值增长率和溢价率轮动债券和境外基金》
46 | 1. 点击“点击更新”按钮,即可更新这2个策略(https://xueqiu.com/P/ZH3040077 和 https://xueqiu.com/P/ZH3044957 )的最新的排名数据。
47 | 2. 从券商PC端软件导出我的持仓放在左侧列表,即可看到目前持仓排名了。
--------------------------------------------------------------------------------
/UniversalRotation.py:
--------------------------------------------------------------------------------
1 | import os, random, time, datetime, schedule, webbrowser
2 | import xlwings
3 | import pandas
4 | import requests
5 | import pysnowball
6 | import browser_cookie3
7 | from chinese_calendar import is_workday
8 |
9 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
10 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
11 |
12 |
13 | @xlwings.func
14 | def get_fund_net_asset_value_history(fund_code: str, total_pages: int = 38, page_sizes: int = 20) -> pandas.DataFrame:
15 | '''
16 | 根据基金代码、获取的总页数和每页数据条数来抓取基金净值信息
17 |
18 | Parameters
19 | ----------
20 | fund_code : 6位基金代码
21 | total_pages : 总页数,用于确定需要抓取的页码范围
22 | page_sizes: 每页获取的数据条数
23 |
24 | Return
25 | ------
26 | DataFrame : 包含基金历史净值数据
27 | '''
28 | # 请求头
29 | EastmoneyFundHeaders = {
30 | 'Accept': '*/*',
31 | 'Accept-Encoding': 'gzip, deflate',
32 | 'Accept-Language': 'zh-CN,zh;q=0.9',
33 | # 'Connection': 'keep-alive',
34 | 'Host': 'api.fund.eastmoney.com',
35 | 'Referer': 'http://fundf10.eastmoney.com/',
36 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
37 | }
38 |
39 | url = 'https://api.fund.eastmoney.com/f10/lsjz'
40 | all_rows = []
41 | columns = ['日期', '单位净值', '累计净值', '涨跌幅']
42 |
43 | # 设置重连次数
44 | requests.adapters.DEFAULT_RETRIES = 10
45 | session1 = requests.session()
46 | session1.keep_alive = False
47 |
48 | for page in range(1, total_pages + 1):
49 | Eastmoneyparams = {
50 | 'fundCode': fund_code,
51 | 'pageIndex': str(page),
52 | 'pageSize': f'{page_sizes}',
53 | 'startDate': '',
54 | 'endDate': '',
55 | }
56 |
57 | response1 = session1.get(url, headers=EastmoneyFundHeaders, params=Eastmoneyparams, verify=False, stream=False,
58 | timeout=10)
59 | json_response = response1.json()
60 | response1.close()
61 | del(response1)
62 |
63 | if json_response is None:
64 | return pandas.DataFrame(all_rows, columns=columns)
65 | datas = json_response.get('Data').get('LSJZList')
66 |
67 | for stock in datas:
68 | all_rows.append({
69 | '日期': stock['FSRQ'],
70 | '单位净值': stock['DWJZ'],
71 | '累计净值': stock['LJJZ'],
72 | '涨跌幅': stock['JZZZL']
73 | })
74 |
75 | df = pandas.DataFrame(all_rows)
76 | df['日期'] = pandas.to_datetime(df['日期'], errors='coerce')
77 | df['单位净值'] = pandas.to_numeric(df['单位净值'], errors='coerce')
78 | df['累计净值'] = pandas.to_numeric(df['累计净值'], errors='coerce')
79 | df['涨跌幅'] = pandas.to_numeric(df['涨跌幅'], errors='coerce')
80 |
81 | return df
82 |
83 |
84 | @xlwings.func
85 | # 获取基金的市价、溢价率、成交量
86 | def get_fund_premium_rate_and_amount(fund_code: str):
87 | detail = pandas.DataFrame(pysnowball.quote_detail(fund_code))
88 | row1=detail.loc["quote"].iloc[0]
89 | premium_rate1 = row1["premium_rate"]
90 | current_price = row1["current"]
91 | amount1 = row1["amount"]
92 | amount1 = amount1/10000 if amount1 else 0
93 | return current_price, premium_rate1, amount1
94 |
95 | @xlwings.func
96 | # 获取xq_a_token
97 | def get_xq_a_token():
98 | str_xq_a_token = ';'
99 | while True:
100 | cj = browser_cookie3.load()
101 | for item in cj:
102 | if item.name == "xq_a_token" :
103 | print('get token, %s = %s' % (item.name, item.value))
104 | str_xq_a_token = 'xq_a_token=' + item.value + ';'
105 | return str_xq_a_token
106 | if str_xq_a_token == ";" :
107 | print('get token, retrying ......')
108 | webbrowser.open("https://xueqiu.com/")
109 | time.sleep(1)
110 |
111 | @xlwings.func
112 | # 根据排名计算排名分(精确到小数点后2位)
113 | def get_rank_value(length: int, rank: int):
114 | return round((length-rank)/length*100, 2)
115 |
116 |
117 | @xlwings.func
118 | def rotate_fund_by_premium_rate_and_20net_asset_value(source_sheets: str, dest_range: str, delay: int):
119 | '''
120 | 根据20天净值增长率和溢价率来量化轮动基金
121 | Parameters
122 | ----------
123 | source_sheets : Excel里sheet名称
124 | dest_range : 写回Excel的区域
125 | delay : 延时
126 | '''
127 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
128 | wb = xlwings.Book.caller() # wb = xlwings.Book(r'UniversalRotation.xlsm')
129 | pandas.options.display.max_columns = None
130 | pandas.options.display.max_rows = None
131 | pysnowball.set_token(get_xq_a_token())
132 |
133 | sheet_fund = wb.sheets[source_sheets]
134 | # 数据表范围请参考Excel里的"最新数据"区域
135 | source_range = 'H2:Y' + str(sheet_fund.used_range.last_cell.row)
136 | print('数据表范围:' + source_range)
137 | data_fund = pandas.DataFrame(sheet_fund.range(source_range).value,
138 | columns=['基金代码','基金名称','投资种类','溢价率','5天净值增长率','10天净值增长率',
139 | '20天净值增长率','60天净值增长率','120天净值增长率','250天净值增长率',
140 | '500天净值增长率','750天净值增长率','总排名分','20天净值增长率排名分',
141 | '溢价率排名分','成交额(万元)','Mark','持有周期'])
142 | refresh_time = str(time.strftime("%Y-%m-%d__%H.%M.%S", time.localtime()))
143 | sheet_fund.range('AA6').value = '更新时间:' + refresh_time
144 | log_file = open('log-' + source_sheets + refresh_time + '.txt', 'a+')
145 |
146 | for i,fund_code in enumerate(data_fund['基金代码']):
147 | # 延时
148 | if (i % 10 == 0) :
149 | time.sleep(random.randrange(delay))
150 |
151 | netAssetValueRaw = get_fund_net_asset_value_history(fund_code[2:8])
152 | # 最新净值以及5个交易日前、10个交易日前、20个交易日前、60个交易日前、120个交易日前、250个交易日前、500个交易日前、750个交易日前的累计净值
153 | netAssetDate1 = str(netAssetValueRaw.loc[0]['日期'])[0:10]
154 | netAssetLJValue1 = netAssetValueRaw.loc[0]['累计净值']
155 | netAssetDate20 = str(netAssetValueRaw.loc[len(netAssetValueRaw)-1]['日期'])[0:10]
156 | netAssetLJValue5 = netAssetLJValue10 = netAssetLJValue20 = netAssetLJValue60 = netAssetLJValue120 \
157 | = netAssetLJValue250 = netAssetLJValue500 \
158 | = netAssetLJValue750 = netAssetValueRaw.loc[len(netAssetValueRaw)-1]['累计净值']
159 | if (len(netAssetValueRaw) > 20) :
160 | netAssetLJValue5 = netAssetValueRaw.loc[5]['累计净值']
161 | netAssetLJValue10 = netAssetValueRaw.loc[10]['累计净值']
162 | netAssetDate20 = str(netAssetValueRaw.loc[20]['日期'])[0:10]
163 | netAssetLJValue20 = netAssetValueRaw.loc[20]['累计净值']
164 | if (len(netAssetValueRaw) > 60) :
165 | netAssetLJValue60 = netAssetValueRaw.loc[60]['累计净值']
166 | if (len(netAssetValueRaw) > 120) :
167 | netAssetLJValue120 = netAssetValueRaw.loc[120]['累计净值']
168 | if (len(netAssetValueRaw) > 250) :
169 | netAssetLJValue250 = netAssetValueRaw.loc[250]['累计净值']
170 | if (len(netAssetValueRaw) > 500) :
171 | netAssetLJValue500 = netAssetValueRaw.loc[500]['累计净值']
172 | if (len(netAssetValueRaw) > 750) :
173 | netAssetLJValue750 = netAssetValueRaw.loc[750]['累计净值']
174 | # 更新5天、10天、20天、60天、120天、250天、500天、750天的累计净值增长率
175 | netAssetLJValue5Rate = round((netAssetLJValue1 - netAssetLJValue5) / netAssetLJValue5, 4)
176 | data_fund.loc[i, '5天净值增长率'] = netAssetLJValue5Rate
177 | netAssetLJValue10Rate = round((netAssetLJValue1 - netAssetLJValue10) / netAssetLJValue10, 4)
178 | data_fund.loc[i, '10天净值增长率'] = netAssetLJValue10Rate
179 | netAssetLJValue20Rate = round((netAssetLJValue1 - netAssetLJValue20) / netAssetLJValue20, 4)
180 | data_fund.loc[i, '20天净值增长率'] = netAssetLJValue20Rate
181 | data_fund.loc[i, '60天净值增长率'] = round((netAssetLJValue1 - netAssetLJValue60) / netAssetLJValue60, 4)
182 | data_fund.loc[i, '120天净值增长率'] = round((netAssetLJValue1 - netAssetLJValue120) / netAssetLJValue120, 4)
183 | data_fund.loc[i, '250天净值增长率'] = round((netAssetLJValue1 - netAssetLJValue250) / netAssetLJValue250, 4)
184 | data_fund.loc[i, '500天净值增长率'] = round((netAssetLJValue1 - netAssetLJValue500) / netAssetLJValue500, 4)
185 | data_fund.loc[i, '750天净值增长率'] = round((netAssetLJValue1 - netAssetLJValue750) / netAssetLJValue750, 4)
186 |
187 | # 更新基金的市价、溢价率、成交量
188 | current_price,fundPremiumRateValue,fundAmount = get_fund_premium_rate_and_amount(fund_code)
189 | netAssetDWValue1 = netAssetValueRaw.loc[0]['单位净值']
190 | if (current_price) :
191 | fundPremiumRateValue = round((current_price - netAssetDWValue1) / netAssetDWValue1, 4)
192 | data_fund.loc[i, '溢价率'] = fundPremiumRateValue
193 | data_fund.loc[i, '成交额(万元)'] = fundAmount
194 |
195 | log_str = 'No.' + format(str(i), "<6") + fund_code + ':'+ format(data_fund.loc[i, '基金名称'], "<15") \
196 | + netAssetDate1 + ':净值:' + format(str(netAssetLJValue1), "<10") \
197 | + netAssetDate20 + ':净值:' + format(str(netAssetLJValue20), "<10") \
198 | + '二十个交易日净值增长率:' + format(str(netAssetLJValue20Rate), "<10") \
199 | + '溢价率:'+ format(str(fundPremiumRateValue), "<10")
200 | print(log_str)
201 | print(log_str, file=log_file)
202 |
203 | # 更新'溢价率排名分'
204 | data_fund = data_fund.sort_values(by='溢价率', ascending=True)
205 | data_fund.reset_index(drop=True, inplace=True)
206 | for i,fundPremiumRateValueItem in enumerate(data_fund['溢价率']):
207 | data_fund.loc[i, '溢价率排名分'] = get_rank_value(len(data_fund), i)
208 |
209 | # 更新'20天净值增长率排名分'
210 | data_fund = data_fund.sort_values(by='20天净值增长率', ascending=False)
211 | data_fund.reset_index(drop=True, inplace=True)
212 | for i,netAssetLJValue20RateItem in enumerate(data_fund['20天净值增长率']):
213 | data_fund.loc[i, '20天净值增长率排名分'] = get_rank_value(len(data_fund), i)
214 |
215 | # 计算'总排名分'
216 | for i,sumValue in enumerate(data_fund['总排名分']):
217 | data_fund.loc[i, '总排名分'] = data_fund.loc[i, '溢价率排名分'] + data_fund.loc[i, '20天净值增长率排名分']
218 | data_fund = data_fund.sort_values(by='总排名分', ascending=False)
219 | data_fund.reset_index(drop=True, inplace=True)
220 | # 根据排名更新'总排名分'
221 | for i,fundPremiumRateValue in enumerate(data_fund['总排名分']):
222 | data_fund.loc[i, '总排名分'] = get_rank_value(len(data_fund), i)
223 | # 将数据起始下标从1开始计数
224 | data_fund.index += 1
225 | print(data_fund)
226 | print(data_fund, file=log_file)
227 |
228 | log_file.close()
229 |
230 | # 更新数据到原Excel
231 | sheet_fund.range(dest_range).value = data_fund
232 | wb.save()
233 |
234 | @xlwings.func
235 | # 轮动20天净值增长和溢价率选LOF、ETF和封基
236 | def rotate_LOF_ETF():
237 | print("------------------------20天净值增长率和溢价率轮动LOF、ETF和封基-------------------------------------------------")
238 | rotate_fund_by_premium_rate_and_20net_asset_value('20天净值增长率和溢价率轮动LOF、ETF和封基','G1', 10)
239 |
240 | @xlwings.func
241 | # 轮动20天净增和溢价率选债券和境外基金
242 | def rotate_abroad_fund():
243 | print("-----------------------20天净值增长率和溢价率轮动债券和境外基金---------------------------------------------------")
244 | rotate_fund_by_premium_rate_and_20net_asset_value('20天净值增长率和溢价率轮动债券和境外基金', 'G1', 10)
245 |
246 | source_range_convertible_bond = 'B8:T'
247 |
248 | # 获取可转债实时数据列表上方的多因子策略的阈值上限和权重
249 | def get_convertible_bond_factor(factor:str):
250 | factor = factor.split(',', -1)
251 | return float(factor[0]), float(factor[1])
252 |
253 | @xlwings.func
254 | # 更新可转债实时数据:价格、涨跌幅、转股价、转股价值、溢价率、双低值、到期时间、剩余年限、剩余规模、成交金额、换手率、税前收益、振幅等
255 | def refresh_convertible_bond():
256 | print("更新可转债实时数据:价格、涨跌幅、转股价、转股价值、溢价率、双低值、到期时间、剩余年限、剩余规模、成交金额、换手率、税前收益、振幅等")
257 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
258 | wb = xlwings.Book.caller()
259 | pandas.options.display.max_columns = None
260 | pandas.options.display.max_rows = None
261 | pysnowball.set_token(get_xq_a_token())
262 |
263 | source_sheets = '可转债实时数据'
264 | sheet_fund = wb.sheets[source_sheets]
265 | source_range = source_range_convertible_bond + str(sheet_fund.used_range.last_cell.row)
266 | print('数据表范围:' + source_range)
267 | data_fund = pandas.DataFrame(sheet_fund.range(source_range).value,
268 | columns=['转债代码','转债名称','当前价','涨跌幅','转股价','转股价值','溢价率','双低值',
269 | '到期时间','剩余年限','剩余规模','成交金额','换手率','税前收益','最高价','最低价',
270 | '振幅','多因子1排名','多因子2排名'])
271 | refresh_time = str(time.strftime("%Y-%m-%d__%H.%M.%S", time.localtime()))
272 | sheet_fund.range('W12').value = '更新时间:' + refresh_time
273 | log_file = open('log-' + source_sheets + refresh_time + '.txt', 'a+')
274 |
275 | for i,fund_code in enumerate(data_fund['转债代码']):
276 | if str(fund_code).startswith('11') or str(fund_code).startswith('13'):
277 | fund_code_str = ('SH' + str(fund_code))[0:8]
278 | elif str(fund_code).startswith('12'):
279 | fund_code_str = ('SZ' + str(fund_code))[0:8]
280 | detail = pandas.DataFrame(pysnowball.quote_detail(fund_code_str))
281 | row1 = detail.loc["quote"].iloc[0]
282 | data_fund.loc[i, '当前价'] = row1["current"]
283 | data_fund.loc[i, '涨跌幅'] = row1["percent"] / 100 if row1["percent"] != None else '停牌'
284 | data_fund.loc[i, '转股价'] = row1["conversion_price"]
285 | data_fund.loc[i, '转股价值'] = row1["conversion_value"]
286 | data_fund.loc[i, '溢价率'] = row1["premium_rate"] / 100 if row1["premium_rate"] != None else '停牌'
287 | data_fund.loc[i, '双低值'] = row1["current"] + row1["premium_rate"]
288 | data_fund.loc[i, '到期时间'] = str(time.strftime("%Y-%m-%d", time.localtime(row1["maturity_date"]/1000)))
289 | data_fund.loc[i, '剩余年限'] = row1["remain_year"]
290 | data_fund.loc[i, '剩余规模'] = row1["outstanding_amt"] / 100000000 if row1["outstanding_amt"] != None else 1
291 | data_fund.loc[i, '成交金额'] = row1["amount"] / 10000 if row1["amount"] != None else 0
292 | data_fund.loc[i, '换手率'] = (data_fund.loc[i, '成交金额'] / 10000 / row1["current"]) / (data_fund.loc[i, '剩余规模'] / 100)
293 | data_fund.loc[i, '税前收益'] = row1["benefit_before_tax"] / 100 if row1["benefit_before_tax"] != None else '停牌'
294 | data_fund.loc[i, '最高价'] = row1["high"]
295 | data_fund.loc[i, '最低价'] = row1["low"]
296 | if row1["high"] and row1["low"]:
297 | data_fund.loc[i, '振幅'] = (row1["high"] - row1["low"]) / row1["low"]
298 | else:
299 | data_fund.loc[i, '振幅'] = '停牌'
300 | log_str = 'No.' + format(str(i), "<6") + format(str(fund_code_str), "<10") \
301 | + format(data_fund.loc[i, '转债名称'], "<15") \
302 | + '当前价:' + format(str(row1["current"]), "<10") \
303 | + '溢价率:' + format(str(row1["premium_rate"]), "<10") \
304 | + '涨跌幅:'+ format(str(row1["percent"]), "<10")
305 | print(log_str)
306 | print(log_str, file=log_file)
307 |
308 | data_fund = data_fund.sort_values(by='溢价率')
309 | data_fund.reset_index(drop=True, inplace=True)
310 | data_fund.index += 1
311 | print(data_fund)
312 | print(data_fund, file=log_file)
313 |
314 | log_file.close()
315 | # 更新原Excel
316 | sheet_fund.range('A7').value = data_fund
317 | wb.save()
318 |
319 | @xlwings.func
320 | # 更新低溢价可转债数据
321 | def refresh_premium_rate_convertible_bond():
322 | print("--------------------------------------更新低溢价可转债数据----------------------------------------------------")
323 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
324 | wb = xlwings.Book.caller()
325 | pandas.options.display.max_columns = None
326 | pandas.options.display.max_rows = None
327 |
328 | sheet_src = wb.sheets['可转债实时数据']
329 | source_range = source_range_convertible_bond + str(sheet_src.used_range.last_cell.row)
330 | data_fund_source = pandas.DataFrame(sheet_src.range(source_range).value,
331 | columns=['转债代码','转债名称','当前价','涨跌幅','转股价','转股价值','溢价率','双低值',
332 | '到期时间','剩余年限','剩余规模','成交金额','换手率','税前收益','最高价','最低价',
333 | '振幅','多因子1排名','多因子2排名'])
334 | data_fund_destination = data_fund_source[['转债代码','转债名称','当前价','溢价率','剩余规模']]
335 | data_fund_destination = data_fund_destination[(data_fund_destination['当前价'] < sheet_src.range('D2').value) &
336 | (data_fund_destination['溢价率'] < sheet_src.range('H2').value) &
337 | (data_fund_destination['剩余规模'] < sheet_src.range('L2').value)]
338 | data_fund_destination = data_fund_destination.sort_values(by='溢价率')
339 | data_fund_destination.reset_index(drop=True, inplace=True)
340 | data_fund_destination.index += 1
341 | print(data_fund_destination)
342 |
343 | # 更新低溢价可转债轮动sheet
344 | sheet_dest = wb.sheets['低溢价可转债轮动']
345 | sheet_dest.range('H2').value = data_fund_destination[:50]
346 | wb.save()
347 |
348 | @xlwings.func
349 | # 更新双低可转债数据
350 | def refresh_price_and_premium_rate_convertible_bond():
351 | print("----------------------------------------更新双低可转债数据----------------------------------------------------")
352 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
353 | wb = xlwings.Book.caller()
354 | pandas.options.display.max_columns = None
355 | pandas.options.display.max_rows = None
356 |
357 | sheet_src = wb.sheets['可转债实时数据']
358 | source_range = source_range_convertible_bond + str(sheet_src.used_range.last_cell.row)
359 | data_fund_source = pandas.DataFrame(sheet_src.range(source_range).value,
360 | columns=['转债代码','转债名称','当前价','涨跌幅','转股价','转股价值','溢价率','双低值',
361 | '到期时间','剩余年限','剩余规模','成交金额','换手率','税前收益','最高价','最低价',
362 | '振幅','多因子1排名','多因子2排名'])
363 | data_fund_destination = data_fund_source[['转债代码','转债名称','当前价','溢价率','双低值','剩余规模']]
364 | data_fund_destination = data_fund_destination[(data_fund_destination['当前价'] < sheet_src.range('D3').value) &
365 | (data_fund_destination['溢价率'] < sheet_src.range('H3').value) &
366 | (data_fund_destination['剩余规模'] < sheet_src.range('L3').value)]
367 | data_fund_destination = data_fund_destination.sort_values(by='双低值')
368 | data_fund_destination.reset_index(drop=True, inplace=True)
369 | data_fund_destination.index +=1
370 | print(data_fund_destination)
371 |
372 | # 更新双低可转债轮动sheet
373 | sheet_dest = wb.sheets['双低可转债轮动']
374 | sheet_dest.range('H2').value = data_fund_destination[:50]
375 | wb.save()
376 |
377 | @xlwings.func
378 | # 更新多因子1可转债数据
379 | def refresh_multifactor1_convertible_bond():
380 | print("--------------------------------------更新多因子1可转债数据----------------------------------------------------")
381 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
382 | wb = xlwings.Book.caller()
383 | pandas.options.display.max_columns = None
384 | pandas.options.display.max_rows = None
385 |
386 | sheet_src = wb.sheets['可转债实时数据']
387 | source_range = source_range_convertible_bond + str(sheet_src.used_range.last_cell.row)
388 | data_fund_source = pandas.DataFrame(sheet_src.range(source_range).value,
389 | columns=['转债代码','转债名称','当前价','涨跌幅','转股价','转股价值','溢价率','双低值',
390 | '到期时间','剩余年限','剩余规模','成交金额','换手率','税前收益','最高价','最低价',
391 | '振幅','多因子1排名','多因子2排名'])
392 | data_fund_destination = data_fund_source[['转债代码','转债名称','当前价','溢价率','剩余规模']]
393 | threshold_current_price, weight_current_price = get_convertible_bond_factor(sheet_src.range('D5').value)
394 | threshold_premium_rate, weight_premium_rate = get_convertible_bond_factor(sheet_src.range('H5').value)
395 | threshold_outstanding_amt, weight_outstanding_amt = get_convertible_bond_factor(sheet_src.range('L5').value)
396 |
397 | # for i, fund_code in enumerate(data_fund_source['转债代码']):
398 | # data_fund_source.loc[i, '多因子1排名'] = data_fund_source.loc[i, '当前价'] * weight_current_price
399 | # + data_fund_source.loc[i, '溢价率'] * weight_premium_rate
400 | # + data_fund_source.loc[i, '剩余规模'] * weight_outstanding_amt
401 |
402 | data_fund_destination = data_fund_destination[(data_fund_destination['当前价'] < threshold_current_price) &
403 | (data_fund_destination['溢价率'] < threshold_premium_rate) &
404 | (data_fund_destination['剩余规模'] < threshold_outstanding_amt)]
405 | data_fund_destination = data_fund_destination.sort_values(by='溢价率')
406 | data_fund_destination.reset_index(drop=True, inplace=True)
407 | data_fund_destination.index += 1
408 | print(data_fund_destination)
409 |
410 | # 更新低溢价可转债轮动sheet
411 | sheet_dest = wb.sheets['低溢价可转债轮动']
412 | sheet_dest.range('Q2').value = data_fund_destination[:50]
413 | wb.save()
414 |
415 | @xlwings.func
416 | # 更新多因子2可转债数据
417 | def refresh_multifactor2_convertible_bond():
418 | print("----------------------------------------更新多因子2可转债数据----------------------------------------------------")
419 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
420 | wb = xlwings.Book.caller()
421 | pandas.options.display.max_columns = None
422 | pandas.options.display.max_rows = None
423 |
424 | sheet_src = wb.sheets['可转债实时数据']
425 | source_range = source_range_convertible_bond + str(sheet_src.used_range.last_cell.row)
426 | data_fund_source = pandas.DataFrame(sheet_src.range(source_range).value,
427 | columns=['转债代码','转债名称','当前价','涨跌幅','转股价','转股价值','溢价率','双低值',
428 | '到期时间','剩余年限','剩余规模','成交金额','换手率','税前收益','最高价','最低价',
429 | '振幅','多因子1排名','多因子2排名'])
430 | data_fund_destination = data_fund_source[['转债代码','转债名称','当前价','溢价率','剩余规模']]
431 | threshold_current_price, weight_current_price = get_convertible_bond_factor(sheet_src.range('D6').value)
432 | threshold_premium_rate, weight_premium_rate = get_convertible_bond_factor(sheet_src.range('H6').value)
433 | threshold_outstanding_amt, weight_outstanding_amt = get_convertible_bond_factor(sheet_src.range('L6').value)
434 | data_fund_destination = data_fund_destination[(data_fund_destination['当前价'] < threshold_current_price) &
435 | (data_fund_destination['溢价率'] < threshold_premium_rate) &
436 | (data_fund_destination['剩余规模'] < threshold_outstanding_amt)]
437 | data_fund_destination = data_fund_destination.sort_values(by='溢价率')
438 | data_fund_destination.reset_index(drop=True, inplace=True)
439 | data_fund_destination.index += 1
440 | print(data_fund_destination)
441 |
442 | # 更新双低可转债轮动sheet
443 | sheet_dest = wb.sheets['双低可转债轮动']
444 | sheet_dest.range('R2').value = data_fund_destination[:50]
445 | wb.save()
446 |
447 | @xlwings.func
448 | # 更新股票实时数据:价格、涨跌幅等
449 | def refresh_stock():
450 | print("更新股票实时数据:价格、涨跌幅等")
451 | xlwings.Book("UniversalRotation.xlsm").set_mock_caller()
452 | wb = xlwings.Book.caller()
453 | pandas.options.display.max_columns = None
454 | pandas.options.display.max_rows = None
455 | pysnowball.set_token(get_xq_a_token())
456 |
457 | source_sheets = '股票实时数据'
458 | sheet_fund = wb.sheets[source_sheets]
459 | source_range = 'B2:E' + str(sheet_fund.used_range.last_cell.row)
460 | print('数据表范围:' + source_range)
461 | data_fund = pandas.DataFrame(sheet_fund.range(source_range).value,
462 | columns=['股票代码','股票名称','当前价','涨跌幅'])
463 | refresh_time = str(time.strftime("%Y-%m-%d__%H.%M.%S", time.localtime()))
464 | sheet_fund.range('W3').value = '更新时间:' + refresh_time
465 | log_file = open('log-' + source_sheets + refresh_time + '.txt', 'a+')
466 |
467 | for i,fund_code in enumerate(data_fund['股票代码']):
468 | detail = pandas.DataFrame(pysnowball.quote_detail(fund_code))
469 | row1 = detail.loc["quote"].iloc[0]
470 | print(str(row1))
471 | # data_fund.loc[i, '当前价'] = row1["current"]
472 | # data_fund.loc[i, '涨跌幅'] = row1["percent"] / 100 if row1["percent"] != None else '停牌'
473 | # data_fund.loc[i, '转股价'] = row1["conversion_price"]
474 | # data_fund.loc[i, '转股价值'] = row1["conversion_value"]
475 | # data_fund.loc[i, '溢价率'] = row1["premium_rate"] / 100 if row1["premium_rate"] != None else '停牌'
476 | # data_fund.loc[i, '双低值'] = row1["current"] + row1["premium_rate"]
477 | # data_fund.loc[i, '到期时间'] = str(time.strftime("%Y-%m-%d", time.localtime(row1["maturity_date"]/1000)))
478 | # data_fund.loc[i, '剩余年限'] = row1["remain_year"]
479 | # data_fund.loc[i, '剩余规模'] = row1["outstanding_amt"] / 100000000 if row1["outstanding_amt"] != None else 1
480 | # data_fund.loc[i, '成交金额'] = row1["amount"] / 10000 if row1["amount"] != None else 0
481 | # data_fund.loc[i, '换手率'] = (data_fund.loc[i, '成交金额'] / 10000 / row1["current"]) / (data_fund.loc[i, '剩余规模'] / 100)
482 | # data_fund.loc[i, '税前收益'] = row1["benefit_before_tax"] / 100 if row1["benefit_before_tax"] != None else '停牌'
483 | # data_fund.loc[i, '最高价'] = row1["high"]
484 | # data_fund.loc[i, '最低价'] = row1["low"]
485 | # if row1["high"] and row1["low"]:
486 | # data_fund.loc[i, '振幅'] = (row1["high"] - row1["low"]) / row1["low"]
487 | # else:
488 | # data_fund.loc[i, '振幅'] = '停牌'
489 | # log_str = 'No.' + format(str(i), "<6") + format(str(fund_code_str), "<10") \
490 | # + format(data_fund.loc[i, '转债名称'], "<15") \
491 | # + '当前价:' + format(str(row1["current"]), "<10") \
492 | # + '溢价率:' + format(str(row1["premium_rate"]), "<10") \
493 | # + '涨跌幅:'+ format(str(row1["percent"]), "<10")
494 | # print(log_str)
495 | # print(log_str, file=log_file)
496 | #
497 | # data_fund = data_fund.sort_values(by='溢价率')
498 | # data_fund.reset_index(drop=True, inplace=True)
499 | # data_fund.index += 1
500 | # print(data_fund)
501 | # print(data_fund, file=log_file)
502 |
503 | log_file.close()
504 | # 更新原Excel
505 | sheet_fund.range('A1').value = data_fund
506 | wb.save()
507 |
508 | def main_function():
509 | # date = datetime.datetime.now().date()
510 | # if not is_workday(date):
511 | # return
512 | webbrowser.open("https://xueqiu.com/")
513 |
514 | #删除旧log
515 | for eachfile in os.listdir('./'):
516 | filename = os.path.join('./', eachfile)
517 | if os.path.isfile(filename) and filename.startswith("./log") :
518 | os.remove(filename)
519 |
520 | rotate_LOF_ETF()
521 |
522 | webbrowser.open("https://xueqiu.com/")
523 |
524 | refresh_convertible_bond()
525 | refresh_premium_rate_convertible_bond()
526 | refresh_price_and_premium_rate_convertible_bond()
527 | refresh_multifactor1_convertible_bond()
528 | refresh_multifactor2_convertible_bond()
529 |
530 | rotate_abroad_fund()
531 |
532 | # 更新股票实时数据
533 | refresh_stock()
534 |
535 | def main():
536 | main_function()
537 | schedule.every().day.at("07:00").do(main_function) # 部署7:00执行更新数据任务
538 | while True:
539 | schedule.run_pending()
540 |
541 | if __name__ == "__main__":
542 | main()
--------------------------------------------------------------------------------
/UniversalRotation.xlsm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wbbyfd/UniversalRotation/e02a9663c0b0675dc98e91a0b7a98fc31bb2ff24/UniversalRotation.xlsm
--------------------------------------------------------------------------------
/res/LSJZ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wbbyfd/UniversalRotation/e02a9663c0b0675dc98e91a0b7a98fc31bb2ff24/res/LSJZ.png
--------------------------------------------------------------------------------
/res/author.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wbbyfd/UniversalRotation/e02a9663c0b0675dc98e91a0b7a98fc31bb2ff24/res/author.png
--------------------------------------------------------------------------------
/res/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wbbyfd/UniversalRotation/e02a9663c0b0675dc98e91a0b7a98fc31bb2ff24/res/download.png
--------------------------------------------------------------------------------
/res/premium_rate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wbbyfd/UniversalRotation/e02a9663c0b0675dc98e91a0b7a98fc31bb2ff24/res/premium_rate.png
--------------------------------------------------------------------------------