├── docs ├── 火把 │ ├── author.md │ ├── futures.md │ ├── project.md │ └── shares.md ├── 罗盘 │ ├── 数据 │ │ ├── tools.md │ │ ├── database.md │ │ ├── read_data.md │ │ ├── write_data.md │ │ └── dicts.md │ ├── 加工&测试&评价 │ │ ├── comment.md │ │ └── process.md │ ├── 通讯 │ │ └── mail.md │ ├── 配置&参数 │ │ ├── state.md │ │ └── homeplace.md │ └── 初始化 │ │ └── initialize.md ├── pics │ └── 一身本事.png ├── css │ └── mkdocstrings.css ├── src │ ├── mkdocstrings │ │ ├── templates │ │ │ └── python │ │ │ │ ├── material │ │ │ │ ├── properties.html │ │ │ │ ├── examples.html │ │ │ │ ├── return.html │ │ │ │ ├── style.css │ │ │ │ ├── exceptions.html │ │ │ │ ├── attributes.html │ │ │ │ ├── parameters.html │ │ │ │ ├── docstring.html │ │ │ │ ├── signature.html │ │ │ │ ├── attribute.html │ │ │ │ ├── module.html │ │ │ │ ├── class.html │ │ │ │ ├── method.html │ │ │ │ ├── function.html │ │ │ │ └── children.html │ │ │ │ └── readthedocs │ │ │ │ ├── return.html │ │ │ │ ├── exceptions.html │ │ │ │ ├── parameters.html │ │ │ │ └── style.css │ │ ├── loggers.py │ │ ├── plugin.py │ │ ├── extension.py │ │ └── handlers │ │ │ ├── python.py │ │ │ └── base.py │ └── mkdocs_autorefs │ │ ├── references.py │ │ └── plugin.py └── index.md ├── pure_ocean_breeze.egg-info ├── dependency_links.txt ├── top_level.txt ├── requires.txt ├── SOURCES.txt └── PKG-INFO ├── .gitattributes ├── .vscode └── settings.json ├── Github同步Pypi操作手册 ├── Actions.jpg ├── secrets.jpg ├── settings.jpg ├── setup_py.jpg ├── workflow.jpg ├── add_password.jpg ├── add_username.jpg ├── start_commit.jpg ├── secrets_again.jpg ├── Publish_python_package.jpg └── Github同步Pypi操作手册.md ├── pure_ocean_breeze ├── jason │ ├── withs │ │ ├── __init__.py │ │ └── requires.py │ ├── labor │ │ ├── __init__.py │ │ ├── db_storage.py │ │ └── comment.py │ ├── state │ │ ├── __init__.py │ │ ├── states.py │ │ ├── homeplace.py │ │ └── decorators.py │ ├── data │ │ ├── __init__.py │ │ ├── dicts.py │ │ ├── database.py │ │ └── read_data.py │ └── __init__.py ├── initialize │ ├── __init__.py │ └── initialize.py └── __init__.py ├── .gitignore ├── README.md ├── 更新日志 ├── 更新日志.md ├── version2.md ├── version4.md └── version3.md ├── database ├── on_clickhouse │ ├── mysql_to_clickhouse.py │ └── ricequant_to_clickhouse.py └── on_mysql │ ├── mat_to_mysql.py │ └── ricequant_to_mysql.py ├── .github └── workflows │ ├── documentation-publish.yml │ ├── python-publish.yml │ └── release.yml ├── LICENSE ├── setup.py ├── CLAUDE.md └── mkdocs.yml /docs/火把/author.md: -------------------------------------------------------------------------------- 1 | * 🌙作者正在快马加鞭赶工中…… 2 | * 即将上线,敬请期待🌟 -------------------------------------------------------------------------------- /docs/火把/futures.md: -------------------------------------------------------------------------------- 1 | * 🌙作者正在快马加鞭赶工中…… 2 | * 即将上线,敬请期待🌟 -------------------------------------------------------------------------------- /docs/火把/project.md: -------------------------------------------------------------------------------- 1 | * 🌙作者正在快马加鞭赶工中…… 2 | * 即将上线,敬请期待🌟 -------------------------------------------------------------------------------- /docs/火把/shares.md: -------------------------------------------------------------------------------- 1 | * 🌙作者正在快马加鞭赶工中…… 2 | * 即将上线,敬请期待🌟 -------------------------------------------------------------------------------- /docs/罗盘/数据/tools.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.data.tools -------------------------------------------------------------------------------- /pure_ocean_breeze.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/罗盘/数据/database.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.data.database -------------------------------------------------------------------------------- /docs/罗盘/加工&测试&评价/comment.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.labor.comment -------------------------------------------------------------------------------- /docs/罗盘/加工&测试&评价/process.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.labor.process -------------------------------------------------------------------------------- /docs/罗盘/数据/read_data.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.data.read_data -------------------------------------------------------------------------------- /docs/罗盘/数据/write_data.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.data.write_data -------------------------------------------------------------------------------- /pure_ocean_breeze.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pure_ocean_breeze 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "windsurfPyright.disableLanguageServices": true 3 | } -------------------------------------------------------------------------------- /docs/pics/一身本事.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/docs/pics/一身本事.png -------------------------------------------------------------------------------- /docs/罗盘/通讯/mail.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.mail.email 2 | 4 | -------------------------------------------------------------------------------- /docs/罗盘/配置&参数/state.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.state.states 2 | -------------------------------------------------------------------------------- /docs/罗盘/配置&参数/homeplace.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.state.homeplace 2 | -------------------------------------------------------------------------------- /Github同步Pypi操作手册/Actions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/Actions.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/secrets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/secrets.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/settings.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/setup_py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/setup_py.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/workflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/workflow.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/add_password.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/add_password.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/add_username.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/add_username.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/start_commit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/start_commit.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/secrets_again.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/secrets_again.jpg -------------------------------------------------------------------------------- /Github同步Pypi操作手册/Publish_python_package.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen-001/pure_ocean_breeze/HEAD/Github同步Pypi操作手册/Publish_python_package.jpg -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/withs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一键简洁加载需要的库 3 | """ 4 | 5 | __all__ = ["requires"] 6 | 7 | from pure_ocean_breeze.jason.withs import requires -------------------------------------------------------------------------------- /pure_ocean_breeze/initialize/__init__.py: -------------------------------------------------------------------------------- 1 | __updated__ = '2022-08-16 16:22:11' 2 | 3 | __all__ = ['initialize'] 4 | 5 | from pure_ocean_breeze.initialize import initialize -------------------------------------------------------------------------------- /docs/css/mkdocstrings.css: -------------------------------------------------------------------------------- 1 | /* Indentation. */ 2 | div.doc-contents:not(.first) { 3 | padding-left: 25px; 4 | border-left: 4px solid rgba(230, 230, 230); 5 | margin-bottom: 80px; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | factor/ 3 | requirements.txt 4 | .obsidian 5 | __pycache__/* 6 | */__pycache__/* 7 | __pycache__ 8 | */__pycache__ 9 | .pyc 10 | *.pyc 11 | */*/*/__pycache__/* 12 | .checkpoints/ -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/labor/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 因子加工和绩效评价等 3 | """ 4 | 5 | __all__ = ["process", "comment"] 6 | 7 | from pure_ocean_breeze.jason.labor import process 8 | from pure_ocean_breeze.jason.labor import comment 9 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/state/__init__.py: -------------------------------------------------------------------------------- 1 | __doc__ = """ 2 | 配置&状态文件,包括相关默认参数STATES和配置的路径homeplace 3 | """ 4 | __all__ = ["states", "homeplace", "decorators"] 5 | 6 | from pure_ocean_breeze.jason.state import states 7 | from pure_ocean_breeze.jason.state import homeplace 8 | from pure_ocean_breeze.jason.state import decorators 9 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/properties.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if properties %} 3 | 4 | {% for property in properties %} 5 | {{ property }} 6 | {% endfor %} 7 | 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/data/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 数据相关,包括读取本地常用的数据,向本地数据库写入数据,以及一些不常见类型文件的读取函数 3 | """ 4 | 5 | __all__ = ["read_data", "dicts", "tools", "database"] 6 | 7 | from pure_ocean_breeze.jason.data import read_data 8 | from pure_ocean_breeze.jason.data import tools 9 | from pure_ocean_breeze.jason.data import dicts 10 | from pure_ocean_breeze.jason.data import database 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pure_ocean_breeze 2 | #### **众人的因子框架** 3 | 4 | #### 作者😉 5 | >* 量价选股因子黄金矿工💁‍♂️ 6 | >* 挖因子兼新技术爱好者💁‍♂️ 7 | >* 感谢每一个教我写代码和教我挖因子的人💐 8 | >* 欢迎交流技术优化&因子灵感&工作信息: 9 | 10 | #### 相关链接🔗 11 | >* [PyPi](https://pypi.org/project/pure-ocean-breeze/) 12 | >* [Github同步到Pypi操作手册](https://github.com/chen-001/pure_ocean_breeze/blob/master/Github同步Pypi操作手册/Github同步Pypi操作手册.md) -------------------------------------------------------------------------------- /docs/罗盘/初始化/initialize.md: -------------------------------------------------------------------------------- 1 | ## 🌟初始化 2 | * 在初次安装框架时,请进行初始化,以将路径设置到自己的文件里 3 | * 使用如下语句进行初始化 4 | ```python 5 | import pure_ocean_breeze.initialize.initialize 6 | pure_ocean_breeze.initialize.initialize.initialize() 7 | ``` 8 | * 然后根据提示进行操作即可(⚠️ 请注意路径不要写反斜杠`\`,而要写成`/`) 9 | * 经过初始化后,以后就可以直接使用,不论重启电脑或者版本升级,都不用再初始化 10 | 11 | ⚠️ 如果更换了数据库路径,请重新初始化 12 | 13 | -------------------------------------------------------------------------------- /更新日志/更新日志.md: -------------------------------------------------------------------------------- 1 | ## 更新日志🗓 2 | 3 | 🥳我们的目标是:功能越来越多,bug越来越少! 4 | 5 | 记录更新日志,不仅记录了回测框架版本的变迁,还记录了每天学习进步的过程,以及为回退旧版本提供便利。 6 | 7 | >* [更新日志version4](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/version4.md) 8 | >* [更新日志version3](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/version3.md) 9 | >* [更新日志version2](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/version2.md) 10 | 11 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/examples.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 |

Examples:

3 | {% for section_type, sub_section in examples %} 4 | {% if section_type == "markdown" %} 5 | {{ sub_section|convert_markdown(heading_level, html_id) }} 6 | {% elif section_type == "examples" %} 7 | {{ sub_section|highlight(language="python", linenums=False) }} 8 | {% endif %} 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/return.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 |

Returns:

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
TypeDescription
{{ return.annotation }}{{ return.description|convert_markdown(heading_level, html_id) }}
17 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/style.css: -------------------------------------------------------------------------------- 1 | /* Don't capitalize names. */ 2 | h5.doc-heading { 3 | text-transform: none !important; 4 | } 5 | 6 | /* Avoid breaking parameters name, etc. in table cells. */ 7 | .doc-contents td code { 8 | word-break: normal !important; 9 | } 10 | 11 | /* For pieces of Markdown rendered in table cells. */ 12 | .doc-contents td p { 13 | margin-top: 0 !important; 14 | margin-bottom: 0 !important; 15 | } 16 | -------------------------------------------------------------------------------- /pure_ocean_breeze/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一个量化多因子研究的框架,包含数据、回测、因子加工等方面的功能 3 | """ 4 | 5 | __updated__ = "2025-06-26 15:19:39" 6 | __version__ = "6.5.0" 7 | __author__ = "chenzongwei" 8 | __author_email__ = "winterwinter999@163.com" 9 | __url__ = "https://github.com/chen-001/pure_ocean_breeze" 10 | __all__ = [ 11 | "initialize", 12 | "jason", 13 | ] 14 | 15 | 16 | import warnings 17 | 18 | warnings.filterwarnings("ignore") 19 | from pure_ocean_breeze.initialize.initialize import * 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/exceptions.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 |

Exceptions:

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for exception in exceptions %} 12 | 13 | 14 | 15 | 16 | {% endfor %} 17 | 18 |
TypeDescription
{{ exception.annotation }}{{ exception.description|convert_markdown(heading_level, html_id) }}
19 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/readthedocs/return.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 |
Returns: 11 |
    12 |
  • {{ ("`" + return.annotation + "` – " + return.description)|convert_markdown(heading_level, html_id) }}
  • 13 |
14 |
18 | -------------------------------------------------------------------------------- /database/on_clickhouse/mysql_to_clickhouse.py: -------------------------------------------------------------------------------- 1 | from pure_ocean_breeze.pure_ocean_breeze import * 2 | 3 | sql=sqlConfig('minute_data') 4 | chc=ClickHouseClient('minute_data') 5 | codes=sql.show_tables(full=False) 6 | 7 | fails=[] 8 | for code in tqdm.tqdm(codes): 9 | try: 10 | df=sql.get_data(code) 11 | (np.around(df,2)*100).ffill().dropna().astype(int).assign(code=code).to_sql('minute_data',chc.engine,if_exists='append',index=False) 12 | except Exception: 13 | fails.append(code) 14 | logger.warning(f'{code}失败了,请检查') 15 | logger.success('存储完啦') 16 | 17 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/attributes.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 |

Attributes:

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for attribute in attributes %} 13 | 14 | 15 | 16 | 17 | 18 | {% endfor %} 19 | 20 |
NameTypeDescription
{{ attribute.name }}{{ attribute.annotation }}{{ attribute.description|convert_markdown(heading_level, html_id) }}
21 | -------------------------------------------------------------------------------- /pure_ocean_breeze.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | pandas<=1.5.3 2 | scipy 3 | statsmodels 4 | plotly 5 | pyarrow 6 | loguru 7 | knockknock 8 | dcube 9 | tenacity 10 | pickledb 11 | pymysql 12 | sqlalchemy 13 | requests 14 | bs4 15 | wrapt_timeout_decorator 16 | pyfinance 17 | texttable 18 | numpy_ext 19 | xpinyin 20 | cufflinks 21 | clickhouse_sqlalchemy 22 | tradetime 23 | deprecation 24 | questdb==1.1.0 25 | mpire 26 | py7zr 27 | unrar 28 | rarfile 29 | chardet 30 | cachier 31 | polars 32 | polars_ols 33 | python_docx 34 | psycopg2-binary 35 | 36 | [linux] 37 | psycopg2-binary 38 | 39 | [macos] 40 | psycopg2-binary 41 | 42 | [windows] 43 | psycopg2 44 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/readthedocs/exceptions.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 |
Exceptions: 11 |
    12 | {% for exception in exceptions %} 13 |
  • {{ ("`" + exception.annotation + "` – " + exception.description)|convert_markdown(heading_level, html_id) }}
  • 14 | {% endfor %} 15 |
16 |
20 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/readthedocs/parameters.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 |
Parameters: 11 |
    12 | {% for parameter in parameters %} 13 |
  • {{ ("**" + parameter.name + "** (`" + parameter.annotation + "`) – " + parameter.description)|convert_markdown(heading_level, html_id) }}
  • 14 | {% endfor %} 15 |
16 |
20 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/parameters.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 |

Parameters:

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for parameter in parameters %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% endfor %} 21 | 22 |
NameTypeDescriptionDefault
{{ parameter.name }}{{ parameter.annotation }}{{ parameter.description|convert_markdown(heading_level, html_id) }}{% if parameter.default %}{{ parameter.default }}{% else %}required{% endif %}
23 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/readthedocs/style.css: -------------------------------------------------------------------------------- 1 | /* Avoid breaking parameters name, etc. in table cells. */ 2 | .doc-contents td code { 3 | word-break: normal !important; 4 | } 5 | 6 | /* For pieces of Markdown rendered in table cells. */ 7 | .doc-contents td p { 8 | margin-top: 0 !important; 9 | margin-bottom: 0 !important; 10 | } 11 | 12 | /* Avoid breaking code headings. */ 13 | .doc-heading code { 14 | white-space: normal; 15 | } 16 | 17 | /* Improve rendering of parameters, returns and exceptions. */ 18 | .doc-contents .field-name { 19 | min-width: 100px; 20 | } 21 | .doc-contents .field-name, .field-body { 22 | border: none !important; 23 | padding: 0 !important; 24 | } 25 | .doc-contents .field-list { 26 | margin: 0 !important; 27 | } 28 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 🎈pure_ocean_breeze的说明文档🌟 2 | 3 | --- 4 | ## 安装&升级 5 | >* 安装:使用`pip install pure_ocean_breeze`命令安装 6 | >* 升级:使用`pip install pure_ocean_breeze --upgrade`命令升级 7 | 8 | ## 简介 9 | >* pure_ocean_breeze是一个关于量化投资,因子研究的python库 10 | >* 包括参数设置、数据获取、数据更新与存储、因子构造、因子加工、因子测试、绩效评价等诸多模块 11 | 12 | ## 全新大版本📢 13 | >* v3.0.0 — 2022.08.16 14 | >>回测框架3.0版本来啦! 模块拆分&说明文档来啦! 15 | >* v2.0.0 — 2022.07.12 16 | >>回测框架2.0版本来啦!数据库&自动更新&最终因子库功能上线啦! 17 | 18 | ## 相关链接🔗 19 | >* [PyPi库主页](https://pypi.org/project/pure-ocean-breeze/) 20 | >* [Github项目主页](https://github.com/chen-001/pure_ocean_breeze) 21 | >* [Github同步到Pypi操作手册](https://github.com/chen-001/pure_ocean_breeze/blob/master/Github同步Pypi操作手册/Github同步Pypi操作手册.md) 22 | >* [更新日志](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/更新日志.md) 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/state/states.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一些默认的参数 3 | """ 4 | 5 | __updated__ = "2023-07-13 12:18:16" 6 | 7 | STATES = { 8 | "NO_LOG": False, 9 | "NO_COMMENT": False, 10 | "NO_SAVE": True, 11 | "NO_PLOT": False, 12 | "START": 20170101, 13 | } 14 | 15 | COMMENTS_WRITER=None 16 | NET_VALUES_WRITER=None 17 | ON_PAPER=False 18 | MOON_START=None 19 | MOON_END=None 20 | 21 | def is_notebook() -> bool: 22 | try: 23 | shell = get_ipython().__class__.__name__ 24 | if shell == 'ZMQInteractiveShell': 25 | return True # Jupyter notebook or qtconsole 26 | elif shell == 'TerminalInteractiveShell': 27 | return False # Terminal running IPython 28 | else: 29 | return False # Other type (?) 30 | except NameError: 31 | return False -------------------------------------------------------------------------------- /更新日志/version2.md: -------------------------------------------------------------------------------- 1 | ## 更新日志🗓 — v2 2 | 3 | * v2.6.7 — 2022.08.13 4 | >1. 修复了基于clickhouse计算分钟数据因子,天数是10的倍数时的bug 5 | >2. 调整画图风格为science+no_latex,及其他设置 6 | >3. 增设读取初级因子功能 7 | >4. 修改因子值排序 8 | >5. 读取最终因子值函数,固定为返回元组 9 | * v2.6.6 — 2022.08.06 10 | >1. 使用black风格格式化了代码,增加可读性 11 | >2. github自动更新至pypi 12 | * v2.6.5 — 2022.08.05 13 | >1. 修复了读取各行业行情数据的bug 14 | >2. 暂时回退了minute_data_file路径参数,修复了pure_fall的bug 15 | * v2.6.4 — 2022.08.02 16 | >1. 修复了以mysql更新分钟因子的bug 17 | >2. 改善了指数超额收益起点时间的运算逻辑 18 | >3. 修复了3510指数行情的bug 19 | * 数据库相关0.0.1 — 2022.08.01 20 | >1. mysql化:增加将wind分钟数据mat文件转存至mysql,米筐分钟数据h5文件转存至mysql 21 | >2. clickhouse化:增加将mysql转存至clickhouse,增加将米筐数据h5文件转存至clickhouse 22 | * v2.6.3 — 2022.07.31 23 | >1. 修复了分钟数据计算因子类pure_fall_frequent和pure_fall_flexible的更新bug 24 | >2. 修复了更新3510指数行情数据的bug,以及读取bug 25 | >3. 将STATES中的默认参数startdate从20100101修改为20130101 -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/state/homeplace.py: -------------------------------------------------------------------------------- 1 | """ 2 | 初始化时保存的路径 3 | """ 4 | 5 | __updated__ = "2025-02-26 15:17:06" 6 | 7 | import os 8 | import pickle 9 | 10 | 11 | class HomePlace(object): 12 | """ 13 | ``` 14 | daily_data_file: 日频数据存放位置 15 | factor_data_file: (辅助、初级)因子数据存放位置 16 | barra_data_file: 十种常用风格因子的存放位置 17 | update_data_file: 更新数据存放位置 18 | ``` 19 | """ 20 | 21 | __slots__ = [ 22 | "daily_data_file", 23 | "factor_data_file", 24 | "barra_data_file", 25 | "update_data_file", 26 | ] 27 | 28 | def __init__(self): 29 | user_file = os.path.expanduser("~") + "/" 30 | path_file = open(user_file + "paths.settings", "rb") 31 | paths = pickle.load(path_file) 32 | for k in self.__slots__: 33 | setattr(self, k, paths[k]) 34 | -------------------------------------------------------------------------------- /.github/workflows/documentation-publish.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Github Pages 2 | on: 3 | pull_request: 4 | 5 | paths: 6 | - "docs/**" 7 | - "mkdocs.yml" 8 | - "pure_ocean_breeze/**" 9 | push: 10 | paths: 11 | - "docs/**" 12 | - "mkdocs.yml" 13 | - "pure_ocean_breeze/**" 14 | 15 | jobs: 16 | deploy: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.x 23 | - uses: actions/cache@v2 24 | with: 25 | key: ${{ github.ref }} 26 | path: .cache 27 | - run: pip install mkdocstrings-python 28 | - run: pip uninstall mkdocs -y 29 | - run: pip install mkdocs-material-zhcorrect 30 | - run: mkdocs build 31 | - run: mkdocs gh-deploy --force 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # action的名称 2 | name: Upload Python Package 3 | 4 | on: 5 | # 当setup.py分支有push时,触发action 6 | pull_request: 7 | 8 | paths: 9 | - "pure_ocean_breeze/__init__.py" 10 | 11 | push: 12 | 13 | paths: 14 | - "pure_ocean_breeze/__init__.py" 15 | 16 | jobs: 17 | deploy: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.x' 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install build 29 | - name: Build package 30 | run: python -m build 31 | - name: Publish package 32 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 33 | with: 34 | password: ${{secrets.PYPI_API_TOKEN}} -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/state/decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | 用于标注函数功能的一些装饰器(用处不大) 3 | """ 4 | 5 | __updated__ = "2025-07-03 15:09:29" 6 | from typing import Iterable 7 | 8 | 9 | def _list_value(x, list_num_order): 10 | if isinstance(x, Iterable): 11 | return x[list_num_order] 12 | else: 13 | return x 14 | 15 | 16 | def _dict_value(x, list_num_order): 17 | dfs = {} 18 | for k, v in x.items(): 19 | if isinstance(v, Iterable): 20 | dfs[k] = v[list_num_order] 21 | else: 22 | dfs[k] = v 23 | return dfs 24 | 25 | 26 | def do_on_dfs(func): 27 | def wrapper(df=None, *args, **kwargs): 28 | if isinstance(df, list) or isinstance(df,tuple): 29 | dfs = [ 30 | func( 31 | i, *[_list_value(i, num) for i in args], **_dict_value(kwargs, num) 32 | ) 33 | for num, i in enumerate(df) 34 | ] 35 | return dfs 36 | else: 37 | return func(df, *args, **kwargs) 38 | 39 | return wrapper 40 | -------------------------------------------------------------------------------- /pure_ocean_breeze.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.md 3 | setup.py 4 | pure_ocean_breeze/__init__.py 5 | pure_ocean_breeze.egg-info/PKG-INFO 6 | pure_ocean_breeze.egg-info/SOURCES.txt 7 | pure_ocean_breeze.egg-info/dependency_links.txt 8 | pure_ocean_breeze.egg-info/requires.txt 9 | pure_ocean_breeze.egg-info/top_level.txt 10 | pure_ocean_breeze/initialize/__init__.py 11 | pure_ocean_breeze/initialize/initialize.py 12 | pure_ocean_breeze/jason/__init__.py 13 | pure_ocean_breeze/jason/data/__init__.py 14 | pure_ocean_breeze/jason/data/database.py 15 | pure_ocean_breeze/jason/data/dicts.py 16 | pure_ocean_breeze/jason/data/read_data.py 17 | pure_ocean_breeze/jason/data/tools.py 18 | pure_ocean_breeze/jason/labor/__init__.py 19 | pure_ocean_breeze/jason/labor/comment.py 20 | pure_ocean_breeze/jason/labor/process.py 21 | pure_ocean_breeze/jason/state/__init__.py 22 | pure_ocean_breeze/jason/state/decorators.py 23 | pure_ocean_breeze/jason/state/homeplace.py 24 | pure_ocean_breeze/jason/state/states.py 25 | pure_ocean_breeze/jason/withs/__init__.py 26 | pure_ocean_breeze/jason/withs/requires.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 chenzongwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/withs/requires.py: -------------------------------------------------------------------------------- 1 | import time 2 | import datetime 3 | import os 4 | 5 | import numpy as np 6 | import pandas as pd 7 | import scipy 8 | import joblib 9 | import tqdm.auto 10 | from loguru import logger 11 | 12 | import matplotlib.pyplot as plt 13 | 14 | plt.rcParams["axes.unicode_minus"] = False 15 | import copy 16 | import pickle 17 | 18 | import matplotlib as mpl 19 | 20 | mpl.rcParams.update(mpl.rcParamsDefault) 21 | 22 | import matplotlib as mpl 23 | import scipy.stats as ss 24 | 25 | 26 | import warnings 27 | from collections.abc import Iterable 28 | from functools import lru_cache, partial, reduce 29 | from typing import Callable, Union 30 | 31 | import plotly.express as pe 32 | import plotly.io as pio 33 | from dateutil.relativedelta import relativedelta 34 | from tenacity import retry 35 | 36 | warnings.filterwarnings("ignore") 37 | 38 | from texttable import Texttable 39 | import cufflinks as cf 40 | cf.set_config_file(offline=True) 41 | from plotly.tools import FigureFactory as FF 42 | import plotly.graph_objects as go 43 | import plotly.tools as plyoo 44 | 45 | import concurrent 46 | import polars as pl 47 | import glob -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/docstring.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if docstring_sections %} 3 | {% for section in docstring_sections %} 4 | {% if section.type == "markdown" %} 5 | {{ section.value|convert_markdown(heading_level, html_id) }} 6 | {% elif section.type == "attributes" %} 7 | {% with attributes = section.value %} 8 | {% include "attributes.html" with context %} 9 | {% endwith %} 10 | {% elif section.type == "parameters" %} 11 | {% with parameters = section.value %} 12 | {% include "parameters.html" with context %} 13 | {% endwith %} 14 | {% elif section.type == "exceptions" %} 15 | {% with exceptions = section.value %} 16 | {% include "exceptions.html" with context %} 17 | {% endwith %} 18 | {% elif section.type == "return" %} 19 | {% with return = section.value %} 20 | {% include "return.html" with context %} 21 | {% endwith %} 22 | {% elif section.type == "examples" %} 23 | {% with examples = section.value %} 24 | {% include "examples.html" with context %} 25 | {% endwith %} 26 | {% endif %} 27 | {% endfor %} 28 | {% endif %} 29 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一个量化多因子研究的框架,包含数据、回测、因子加工等方面的功能 3 | """ 4 | 5 | __author__ = "chenzongwei" 6 | __author_email__ = "winterwinter999@163.com" 7 | __url__ = "https://github.com/chen-001/pure_ocean_breeze" 8 | __all__ = [ 9 | "data", 10 | "labor", 11 | "state", 12 | "withs" 13 | ] 14 | 15 | import warnings 16 | 17 | warnings.filterwarnings("ignore") 18 | from pure_ocean_breeze.jason.state.homeplace import HomePlace 19 | 20 | 21 | import pickledb 22 | import datetime 23 | 24 | 25 | from pure_ocean_breeze.jason import state 26 | from pure_ocean_breeze.jason import data 27 | from pure_ocean_breeze.jason import labor 28 | 29 | 30 | from pure_ocean_breeze.jason.state import * 31 | from pure_ocean_breeze.jason.data import * 32 | from pure_ocean_breeze.jason.labor import * 33 | 34 | from pure_ocean_breeze.jason.state.homeplace import * 35 | from pure_ocean_breeze.jason.state.decorators import * 36 | 37 | from pure_ocean_breeze.jason.data.dicts import * 38 | from pure_ocean_breeze.jason.data.read_data import * 39 | from pure_ocean_breeze.jason.data.tools import * 40 | from pure_ocean_breeze.jason.data.database import * 41 | 42 | from pure_ocean_breeze.jason.labor.process import * 43 | from pure_ocean_breeze.jason.labor.comment import * 44 | 45 | import sys 46 | # sys.path.append('/home/chenzongwei/pythoncode') 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/signature.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {%- if signature -%} 3 | {%- with -%} 4 | {%- set ns = namespace(render_pos_only_separator=True, render_kw_only_separator=True, equal="=") -%} 5 | 6 | {%- if config.show_signature_annotations -%} 7 | {%- set ns.equal = " = " -%} 8 | {%- endif -%} 9 | 10 | ({%- for parameter in signature.parameters %}{% if parameter.kind == "POSITIONAL_ONLY" -%} 11 | {%- if ns.render_pos_only_separator -%} 12 | {%- set ns.render_pos_only_separator = False %}/, {% endif -%} 13 | {%- elif parameter.kind == "KEYWORD_ONLY" -%} 14 | {%- if ns.render_kw_only_separator -%} 15 | {%- set ns.render_kw_only_separator = False %}*, {% endif -%} 16 | {%- endif -%} 17 | {%- if config.show_signature_annotations and "annotation" in parameter -%} 18 | {%- set annotation = ": " + parameter.annotation|safe -%} 19 | {%- endif -%} 20 | {%- if "default" in parameter -%} 21 | {%- set default = ns.equal + parameter.default|safe -%} 22 | {%- endif -%} 23 | {%- if parameter.kind == "VAR_POSITIONAL" %}* 24 | {%- set render_kw_only_separator = False -%} 25 | {%- elif parameter.kind == "VAR_KEYWORD" %}** 26 | {%- endif %}{{ parameter.name }}{{ annotation }}{{ default }}{% if not loop.last %}, {% endif -%} 27 | {%- endfor %}){% if config.show_signature_annotations and "return_annotation" in signature %} -> {{ signature.return_annotation }} 28 | {%- endif -%} 29 | 30 | {%- endwith -%} 31 | {%- endif -%} 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __updated__ = "2025-03-19 01:47:42" 2 | 3 | from setuptools import setup 4 | import setuptools 5 | import re 6 | import os 7 | import sys 8 | 9 | with open("README.md", "r", encoding="utf-8") as fh: 10 | long_description = fh.read() 11 | 12 | 13 | def get_version(package): 14 | """Return package version as listed in `__version__` in `init.py`.""" 15 | init_py = open(os.path.join(package, "__init__.py")).read() 16 | return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) 17 | 18 | 19 | install_requires = [ 20 | # "numpy", 21 | "pandas<=1.5.3", 22 | "scipy", 23 | "plotly", 24 | # "matplotlib", 25 | "pyarrow", 26 | "loguru", 27 | "tenacity", 28 | "pickledb", 29 | "cufflinks", 30 | "cachier", 31 | "polars", 32 | "polars_ols", 33 | "altair", 34 | ] 35 | if sys.platform.startswith("win"): 36 | install_requires = install_requires + ["psycopg2"] 37 | else: 38 | install_requires = install_requires + ["psycopg2-binary"] 39 | 40 | setup( 41 | name="pure_ocean_breeze", 42 | version=get_version("pure_ocean_breeze"), 43 | description="stock factor test", 44 | # long_description="详见homepage\nhttps://github.com/chen-001/pure_ocean_breeze.git", 45 | long_description=long_description, 46 | long_description_content_type="text/markdown", 47 | author="chenzongwei", 48 | author_email="winterwinter999@163.com", 49 | url="https://github.com/chen-001/pure_ocean_breeze.git", 50 | project_urls={"Documentation": "https://chen-001.github.io/pure_ocean_breeze/"}, 51 | install_requires=install_requires, 52 | python_requires=">=3", 53 | license="MIT", 54 | packages=setuptools.find_packages(), 55 | requires=[], 56 | extras_require={ 57 | "windows": ["psycopg2"], 58 | "macos": ["psycopg2-binary"], 59 | "linux": ["psycopg2-binary"], 60 | }, 61 | ) 62 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## 项目概述 6 | 这是一个量化多因子研究框架,专注于股票因子测试和分析。主要模块包括: 7 | - `initialize`: 初始化配置和数据路径设置 8 | - `jason.data`: 数据读取、处理和数据库操作 9 | - `jason.labor`: 数据加工、回测和评价 10 | - `jason.state`: 配置管理和装饰器 11 | - `jason.withs`: 依赖管理 12 | 13 | ## 开发环境 14 | - Python解释器: `/home/chenzongwei/.conda/envs/chenzongwei311/bin/python` 15 | - Pip包管理: `/home/chenzongwei/.conda/envs/chenzongwei311/bin/pip` 16 | 17 | ## 核心命令 18 | 19 | ### 包构建与发布 20 | ```bash 21 | # 构建包 22 | /home/chenzongwei/.conda/envs/chenzongwei311/bin/python setup.py sdist bdist_wheel 23 | 24 | # 安装本地开发版本 25 | /home/chenzongwei/.conda/envs/chenzongwei311/bin/pip install -e . 26 | 27 | # 测试包导入 28 | /home/chenzongwei/.conda/envs/chenzongwei311/bin/python -c "import pure_ocean_breeze; print('包可以正常导入')" 29 | ``` 30 | 31 | ### 文档构建 32 | ```bash 33 | # 启动文档服务器 34 | mkdocs serve 35 | 36 | # 构建文档 37 | mkdocs build 38 | ``` 39 | 40 | ## 架构设计 41 | 42 | ### 模块结构 43 | - `jason/data/`: 数据层 44 | - `read_data.py`: 读取日频、市场数据 45 | - `tools.py`: 数据处理工具函数,支持lru_cache优化 46 | - `database.py`: 数据库操作 47 | - `dicts.py`: 数据字典定义 48 | - `jason/labor/`: 计算层 49 | - `process.py`: 数据加工和回测主模块 50 | - `comment.py`: 评价和注释功能 51 | - `jason/state/`: 状态管理层 52 | - `homeplace.py`: 主配置类HomePlace 53 | - `decorators.py`: 装饰器工具 54 | - `states.py`: 状态管理 55 | 56 | ### 核心依赖 57 | - 数据处理: pandas, polars, numpy, scipy 58 | - 可视化: altair, plotly (避免使用matplotlib) 59 | - 数据库: psycopg2, pyarrow 60 | - 加速计算: rust_pyfunc, pandarallel 61 | - 缓存优化: cachier, lru_cache 62 | 63 | ### 初始化流程 64 | 使用`pure_ocean_breeze.initialize.ini()`进行首次设置,配置: 65 | - 日频数据路径 66 | - 因子数据路径 67 | - barra数据路径 68 | - 更新数据路径 69 | 70 | 配置信息会保存到用户目录下的pickle文件中。 71 | 72 | ## 开发注意事项 73 | - 项目无单元测试框架,需要手动验证功能 74 | - 使用Altair和Plotly进行数据可视化 75 | - 代码中集成了多进程优化(pandarallel)和Rust加速(rust_pyfunc) 76 | - 支持Jupyter Notebook环境检测 77 | - 通过装饰器`@do_on_dfs`支持DataFrame操作的统一处理 -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/labor/db_storage.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | 4 | def save_factor_return(factor_name: str, neu_ret: float, save_dir: str, db_filename: str) -> bool: 5 | """ 6 | 保存因子收益率到 SQLite 数据库(线程安全) 7 | 8 | Args: 9 | factor_name: 因子名称 10 | neu_ret: 因子收益率 11 | save_dir: 保存目录(与图表同目录) 12 | db_filename: 数据库文件名(不带扩展名) 13 | 14 | Returns: 15 | 是否保存成功 16 | """ 17 | try: 18 | db_path = os.path.join(save_dir, f"{db_filename}.db") 19 | 20 | conn = sqlite3.connect(db_path) 21 | # 创建表(如果不存在) 22 | conn.execute(''' 23 | CREATE TABLE IF NOT EXISTS factor_returns ( 24 | factor_name TEXT PRIMARY KEY, 25 | neu_ret REAL NOT NULL 26 | ) 27 | ''') 28 | # 使用 INSERT OR REPLACE 支持重复写入 29 | conn.execute( 30 | 'INSERT OR REPLACE INTO factor_returns (factor_name, neu_ret) VALUES (?, ?)', 31 | (factor_name, neu_ret) 32 | ) 33 | conn.commit() 34 | conn.close() 35 | return True 36 | except Exception as e: 37 | print(f"保存因子收益率失败: {e}") 38 | return False 39 | 40 | def get_all_factor_returns(save_dir: str, db_filename: str) -> dict: 41 | """ 42 | 获取所有因子收益率 43 | 44 | Args: 45 | save_dir: 保存目录(与图表同目录) 46 | db_filename: 数据库文件名(不带扩展名) 47 | 48 | Returns: 49 | 字典:{factor_name: neu_ret} 50 | """ 51 | try: 52 | db_path = os.path.join(save_dir, f"{db_filename}.db") 53 | 54 | if not os.path.exists(db_path): 55 | return {} 56 | 57 | conn = sqlite3.connect(db_path) 58 | cursor = conn.execute('SELECT factor_name, neu_ret FROM factor_returns') 59 | result = {row[0]: row[1] for row in cursor.fetchall()} 60 | conn.close() 61 | return result 62 | except Exception as e: 63 | print(f"读取因子收益率失败: {e}") 64 | return {} 65 | -------------------------------------------------------------------------------- /database/on_clickhouse/ricequant_to_clickhouse.py: -------------------------------------------------------------------------------- 1 | from pure_ocean_breeze.pure_ocean_breeze import * 2 | 3 | mipath='/Users/chenzongwei/pythoncode/数据库/Ricequant-minbar/equities' 4 | sql=sqlConfig('minute_data') 5 | chc=ClickHouseClient('minute_data') 6 | files=sorted(os.listdir(mipath)) 7 | files=[mipath+'/'+i for i in files] 8 | fails=[] 9 | 10 | for path in tqdm.tqdm(files): 11 | code,kind=convert_code(path) 12 | df=read_h5_new(path) 13 | df=df.rename(columns={'datetime':'date','volume':'amount','total_turnover':'money'}) 14 | df=df[['date','open','high','low','close','amount','money']].sort_values('date') 15 | df.date=df.date.astype(str).str.slice(stop=8).astype(int) 16 | df=df.groupby('date').apply(lambda x:x.assign(num=list(range(1,x.shape[0]+1)))) 17 | df=(np.around(df,2)*100).ffill().dropna().astype(int).assign(code=code) 18 | try: 19 | if kind=='stock': 20 | df.to_sql('minute_data_stock',chc.engine,if_exists='append',index=False) 21 | else: 22 | df.to_sql('minute_data_index',chc.engine,if_exists='append',index=False) 23 | except Exception: 24 | fails.append(code) 25 | logger.warning(f'{code}失败了,请检查') 26 | logger.success('存储完啦') 27 | 28 | # again=[i for i in files if fails[0][:6] in i][0] 29 | # again=[i for i in files if '300671' in i][0] 30 | # 31 | # code,kind=convert_code(again) 32 | # df=read_h5_new(again) 33 | # df=df.rename(columns={'datetime':'date','volume':'amount','total_turnover':'money'}) 34 | # df=df[['date','open','high','low','close','amount','money']].sort_values('date') 35 | # df.date=df.date.astype(str).str.slice(stop=8).astype(int) 36 | # df=df.groupby('date').apply(lambda x:x.assign(num=list(range(1,x.shape[0]+1)))) 37 | # df=(np.around(df,2)*100).ffill().dropna().astype(int).assign(code=code) 38 | # df.to_sql('minute_data_stock',chc.engine,if_exists='append',index=False) 39 | # 40 | # chc.get_data("select * from minute_data.minute_data_stock where code='300671.SZ'") -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/attribute.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if config.show_if_no_docstring or attribute.has_contents %} 3 | 4 |
5 | {% with html_id = attribute.path %} 6 | 7 | {% if not root or config.show_root_heading %} 8 | 9 | {% if root %} 10 | {% set show_full_path = config.show_root_full_path %} 11 | {% set root_members = True %} 12 | {% elif root_members %} 13 | {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} 14 | {% set root_members = False %} 15 | {% else %} 16 | {% set show_full_path = config.show_object_full_path %} 17 | {% endif %} 18 | 19 | {% filter heading(heading_level, 20 | id=html_id, 21 | class="doc doc-heading", 22 | toc_label=attribute.name) %} 23 | 24 | {% filter highlight(language="python", inline=True) %} 25 | {% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %} 26 | {% if attribute.type %}: {{ attribute.type }}{% endif %} 27 | {% endfilter %} 28 | 29 | {% with properties = attribute.properties %} 30 | {% include "properties.html" with context %} 31 | {% endwith %} 32 | 33 | {% endfilter %} 34 | 35 | {% else %} 36 | {% if config.show_root_toc_entry %} 37 | {% filter heading(heading_level, 38 | id=html_id, 39 | toc_label=attribute.path, 40 | hidden=True) %} 41 | {% endfilter %} 42 | {% endif %} 43 | {% set heading_level = heading_level - 1 %} 44 | {% endif %} 45 | 46 |
47 | {% with docstring_sections = attribute.docstring_sections %} 48 | {% include "docstring.html" with context %} 49 | {% endwith %} 50 |
51 | 52 | {% endwith %} 53 |
54 | 55 | {% endif %} 56 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/module.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if config.show_if_no_docstring or module.has_contents %} 3 | 4 |
5 | {% with html_id = module.path %} 6 | 7 | {% if not root or config.show_root_heading %} 8 | 9 | {% if root %} 10 | {% set show_full_path = config.show_root_full_path %} 11 | {% set root_members = True %} 12 | {% elif root_members %} 13 | {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} 14 | {% set root_members = False %} 15 | {% else %} 16 | {% set show_full_path = config.show_object_full_path %} 17 | {% endif %} 18 | 19 | {% filter heading(heading_level, 20 | id=html_id, 21 | class="doc doc-heading", 22 | toc_label=module.name) %} 23 | 24 | {% if show_full_path %}{{ module.path }}{% else %}{{ module.name }}{% endif %} 25 | 26 | {% with properties = module.properties %} 27 | {% include "properties.html" with context %} 28 | {% endwith %} 29 | 30 | {% endfilter %} 31 | 32 | {% else %} 33 | {% if config.show_root_toc_entry %} 34 | {% filter heading(heading_level, 35 | id=html_id, 36 | toc_label=module.path, 37 | hidden=True) %} 38 | {% endfilter %} 39 | {% endif %} 40 | {% set heading_level = heading_level - 1 %} 41 | {% endif %} 42 | 43 |
44 | {% with docstring_sections = module.docstring_sections %} 45 | {% include "docstring.html" with context %} 46 | {% endwith %} 47 | 48 | {% with obj = module %} 49 | {% set root = False %} 50 | {% set heading_level = heading_level + 1 %} 51 | {% include "children.html" with context %} 52 | {% endwith %} 53 |
54 | 55 | {% endwith %} 56 |
57 | 58 | {% endif %} 59 | -------------------------------------------------------------------------------- /pure_ocean_breeze/initialize/initialize.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import os 3 | 4 | 5 | def ini(): 6 | user_file = os.path.expanduser("~") + "/" 7 | # 日频数据路径 8 | daily_data_file = input("请设置日频数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 9 | while "/" not in daily_data_file: 10 | print("请不要输入反斜杠'',请替换为'/',并以'/'结尾") 11 | daily_data_file = input("请设置日频数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 12 | if daily_data_file[-1] != "/": 13 | daily_data_file = daily_data_file + "/" 14 | # 因子数据路径 15 | factor_data_file = input("请设置因子数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 16 | while "/" not in factor_data_file: 17 | print("请不要输入反斜杠'',请替换为'/',并以'/'结尾") 18 | factor_data_file = input("请设置因子数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 19 | if factor_data_file[-1] != "/": 20 | factor_data_file = factor_data_file + "/" 21 | # 风格数据路径 22 | barra_data_file = input("请设置barra数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 23 | while "/" not in barra_data_file: 24 | print("请不要输入反斜杠'',请替换为'/',并以'/'结尾") 25 | barra_data_file = input("请设置barra数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 26 | if barra_data_file[-1] != "/": 27 | barra_data_file = barra_data_file + "/" 28 | # 更新数据路径 29 | update_data_file = input("请设置更新数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 30 | while "/" not in update_data_file: 31 | print("请不要输入反斜杠'',请替换为'/',并以'/'结尾") 32 | update_data_file = input("请设置更新数据存放路径(请最终以斜杠结尾,请不要输入反斜杠'',请都替换为'/'):") 33 | if update_data_file[-1] != "/": 34 | update_data_file = update_data_file + "/" 35 | # use all parts 36 | save_dict = { 37 | "daily_data_file": daily_data_file, 38 | "factor_data_file": factor_data_file, 39 | "barra_data_file": barra_data_file, 40 | "update_data_file": update_data_file, 41 | } 42 | save_dict_file = open(user_file + "paths.settings", "wb") 43 | pickle.dump(save_dict, save_dict_file) 44 | save_dict_file.close() 45 | from loguru import logger 46 | 47 | logger.success("恭喜你,回测框架初始化完成,可以开始使用了👏") 48 | -------------------------------------------------------------------------------- /database/on_mysql/mat_to_mysql.py: -------------------------------------------------------------------------------- 1 | from pure_ocean_breeze.pure_ocean_breeze import * 2 | 3 | '''创建分钟数据的数据库''' 4 | #db_user为用户名,db_password为密码 5 | sql=sqlConfig(db_user='root',db_host='127.0.0.1',db_port=3306,db_password='Kingwila98') 6 | sql.add_new_database('minute_data') 7 | sql.add_new_database('minute_data_alter') 8 | 9 | '''将分钟数据写入sql,每个股票一张表''' 10 | s=sqlConfig(db_name='minute_data') 11 | fs=sorted(os.listdir(homeplace.minute_data_file)) 12 | fs=[i for i in fs if i.endswith('.mat')] 13 | for f in tqdm.tqdm(fs): 14 | k,v=read_minute_mat(f) 15 | v=v.where(v<1e38,np.nan).where(v>-1e38,np.nan) 16 | v.to_sql(name=k,con=s.engine,if_exists='replace',index=False, 17 | dtype={'date':INT,'open':FLOAT(2),'high':FLOAT(2),'low':FLOAT(2),'close':FLOAT(2),'amount':INT,'money':FLOAT(2),'num':INT}) 18 | logger.success('minute_data数据库,即每个股票一张表已经写入完成') 19 | 20 | 21 | '''将分钟数据写入sql,每天一张表''' 22 | front=list(range(2013,2023)) 23 | behind=list(range(2013,2024)) 24 | 25 | def single_year(f,b): 26 | sa=sqlConfig('minute_data_alter') 27 | f,b=f*10000,b*10000 28 | #读入全部分钟数据 29 | dfs=[] 30 | for file in tqdm.tqdm(fs,desc='文件读取中'): 31 | code,df=read_minute_mat(file) 32 | df=df[(df.date>f)&(df.date-1e38,np.nan) 34 | df=df.assign(code=code) 35 | dfs.append(df) 36 | dfs=pd.concat(dfs) 37 | #获取期间的交易日历 38 | f1,f2=str(f+101),str(b+1231-10000) 39 | dates=list(map(int,sorted(list(set(pro.a_calendar(start_date=f1,end_date=f2).trade_date))))) 40 | #拆分并逐个写入 41 | for day in tqdm.tqdm(dates,desc='逐日写入中'): 42 | df=dfs[dfs.date==day] 43 | df=df.drop(columns='date') 44 | if df.shape[0]>0: 45 | df.to_sql(name=str(day),con=sa.engine,if_exists='replace',index=False, 46 | dtype={'open':FLOAT(2),'high':FLOAT(2),'low':FLOAT(2),'close':FLOAT(2),'amount':INT,'money':FLOAT(2),'num':INT,'code':VARCHAR(9)}) 47 | 48 | for f,b in zip(front,behind): 49 | single_year(f,b) 50 | logger.success('minute_data_alter数据库,即每天一张表已经写入完成') 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | # on: 4 | # create: 5 | # branches: 6 | # - master 7 | 8 | # # Sequence of patterns matched against refs/tags 9 | # tags: 10 | # - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 11 | # # pull_request: 12 | # # branches: 13 | # # - master 14 | # # # Sequence of patterns matched against refs/tags 15 | # # tags: 16 | # # - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 17 | # name: Create Release 18 | 19 | # jobs: 20 | # build: 21 | # name: Create Release 22 | # runs-on: ubuntu-latest 23 | # steps: 24 | # - name: Checkout code 25 | # uses: actions/checkout@v2 26 | # # - name: Get version 27 | # # id: get_version 28 | # # run: echo ::set-output name=VERSION::${tags/} 29 | # - name: Create Tag 30 | # id: create_tag 31 | # uses: jaywcjlove/create-tag-action@main 32 | # with: 33 | # test: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 34 | # - name: Create Release 35 | # uses: actions/create-release@v1 36 | # env: 37 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | # with: 39 | # tag_name: v${{ steps.create_tag.outputs.version }} 40 | # release_name: v${{ steps.create_tag.outputs.version }} 41 | # body: 详见[更新日志](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/更新日志.md) 42 | # draft: false 43 | # prerelease: false 44 | 45 | 46 | name: Release 47 | 48 | on: 49 | create: 50 | tags: 51 | - 'v*' 52 | 53 | jobs: 54 | release: 55 | name: Create Release 56 | runs-on: ubuntu-latest 57 | steps: 58 | 59 | - name: Checkout code 60 | uses: actions/checkout@v2 61 | - name: Create Release 62 | id: create_release 63 | uses: actions/create-release@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | tag_name: ${{ github.ref }} 68 | release_name: ${{ github.ref }} 69 | body: 详见[更新日志](https://github.com/chen-001/pure_ocean_breeze/blob/master/更新日志/更新日志.md) 70 | draft: false 71 | prerelease: false 72 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/class.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if config.show_if_no_docstring or class.has_contents %} 3 | 4 |
5 | {% with html_id = class.path %} 6 | 7 | {% if not root or config.show_root_heading %} 8 | 9 | {% if root %} 10 | {% set show_full_path = config.show_root_full_path %} 11 | {% set root_members = True %} 12 | {% elif root_members %} 13 | {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} 14 | {% set root_members = False %} 15 | {% else %} 16 | {% set show_full_path = config.show_object_full_path %} 17 | {% endif %} 18 | 19 | {% filter heading(heading_level, 20 | id=html_id, 21 | class="doc doc-heading", 22 | toc_label=class.name) %} 23 | 24 | {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %} 25 | 26 | {% with properties = class.properties %} 27 | {% include "properties.html" with context %} 28 | {% endwith %} 29 | 30 | {% endfilter %} 31 | 32 | {% else %} 33 | {% if config.show_root_toc_entry %} 34 | {% filter heading(heading_level, 35 | id=html_id, 36 | toc_label=class.path, 37 | hidden=True) %} 38 | {% endfilter %} 39 | {% endif %} 40 | {% set heading_level = heading_level - 1 %} 41 | {% endif %} 42 | 43 |
44 | {% with docstring_sections = class.docstring_sections %} 45 | {% include "docstring.html" with context %} 46 | {% endwith %} 47 | 48 | {% if config.show_source and class.source %} 49 |
50 | Source code in {{ class.relative_file_path }} 51 | {{ class.source.code|highlight(language="python", linestart=class.source.line_start, linenums=False) }} 52 |
53 | {% endif %} 54 | 55 | {% with obj = class %} 56 | {% set root = False %} 57 | {% set heading_level = heading_level + 1 %} 58 | {% include "children.html" with context %} 59 | {% endwith %} 60 |
61 | 62 | {% endwith %} 63 |
64 | 65 | {% endif %} 66 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/method.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if config.show_if_no_docstring or method.has_contents %} 3 | 4 |
5 | {% with html_id = method.path %} 6 | 7 | {% if not root or config.show_root_heading %} 8 | 9 | {% if root %} 10 | {% set show_full_path = config.show_root_full_path %} 11 | {% set root_members = True %} 12 | {% elif root_members %} 13 | {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} 14 | {% set root_members = False %} 15 | {% else %} 16 | {% set show_full_path = config.show_object_full_path %} 17 | {% endif %} 18 | 19 | {% filter heading(heading_level, 20 | id=html_id, 21 | class="doc doc-heading", 22 | toc_label=method.name ~ "()") %} 23 | 24 | {% filter highlight(language="python", inline=True) %} 25 | {% if show_full_path %}{{ method.path }}{% else %}{{ method.name }}{% endif %} 26 | {% with signature = method.signature %}{% include "signature.html" with context %}{% endwith %} 27 | {% endfilter %} 28 | 29 | {% with properties = method.properties %} 30 | {% include "properties.html" with context %} 31 | {% endwith %} 32 | 33 | {% endfilter %} 34 | 35 | {% else %} 36 | {% if config.show_root_toc_entry %} 37 | {% filter heading(heading_level, 38 | id=html_id, 39 | toc_label=method.path, 40 | hidden=True) %} 41 | {% endfilter %} 42 | {% endif %} 43 | {% set heading_level = heading_level - 1 %} 44 | {% endif %} 45 | 46 |
47 | {% with docstring_sections = method.docstring_sections %} 48 | {% include "docstring.html" with context %} 49 | {% endwith %} 50 | 51 | {% if config.show_source and method.source %} 52 |
53 | Source code in {{ method.relative_file_path }} 54 | {{ method.source.code|highlight(language="python", linestart=method.source.line_start, linenums=False) }} 55 |
56 | {% endif %} 57 |
58 | 59 | {% endwith %} 60 |
61 | 62 | {% endif %} 63 | -------------------------------------------------------------------------------- /pure_ocean_breeze.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.2 2 | Name: pure_ocean_breeze 3 | Version: 6.1.0 4 | Summary: stock factor test 5 | Home-page: https://github.com/chen-001/pure_ocean_breeze.git 6 | Author: chenzongwei 7 | Author-email: winterwinter999@163.com 8 | License: MIT 9 | Project-URL: Documentation, https://chen-001.github.io/pure_ocean_breeze/ 10 | Requires-Python: >=3 11 | Description-Content-Type: text/markdown 12 | License-File: LICENSE 13 | Requires-Dist: pandas<=1.5.3 14 | Requires-Dist: scipy 15 | Requires-Dist: statsmodels 16 | Requires-Dist: plotly 17 | Requires-Dist: pyarrow 18 | Requires-Dist: loguru 19 | Requires-Dist: knockknock 20 | Requires-Dist: dcube 21 | Requires-Dist: tenacity 22 | Requires-Dist: pickledb 23 | Requires-Dist: pymysql 24 | Requires-Dist: sqlalchemy 25 | Requires-Dist: requests 26 | Requires-Dist: bs4 27 | Requires-Dist: wrapt_timeout_decorator 28 | Requires-Dist: pyfinance 29 | Requires-Dist: texttable 30 | Requires-Dist: numpy_ext 31 | Requires-Dist: xpinyin 32 | Requires-Dist: cufflinks 33 | Requires-Dist: clickhouse_sqlalchemy 34 | Requires-Dist: tradetime 35 | Requires-Dist: deprecation 36 | Requires-Dist: questdb==1.1.0 37 | Requires-Dist: mpire 38 | Requires-Dist: py7zr 39 | Requires-Dist: unrar 40 | Requires-Dist: rarfile 41 | Requires-Dist: chardet 42 | Requires-Dist: cachier 43 | Requires-Dist: polars 44 | Requires-Dist: polars_ols 45 | Requires-Dist: python_docx 46 | Requires-Dist: psycopg2-binary 47 | Provides-Extra: windows 48 | Requires-Dist: psycopg2; extra == "windows" 49 | Provides-Extra: macos 50 | Requires-Dist: psycopg2-binary; extra == "macos" 51 | Provides-Extra: linux 52 | Requires-Dist: psycopg2-binary; extra == "linux" 53 | Dynamic: author 54 | Dynamic: author-email 55 | Dynamic: description 56 | Dynamic: description-content-type 57 | Dynamic: home-page 58 | Dynamic: license 59 | Dynamic: project-url 60 | Dynamic: provides-extra 61 | Dynamic: requires-dist 62 | Dynamic: requires-python 63 | Dynamic: summary 64 | 65 | # pure_ocean_breeze 66 | #### **众人的因子框架** 67 | 68 | #### 作者😉 69 | >* 量价选股因子黄金矿工💁‍♂️ 70 | >* 挖因子兼新技术爱好者💁‍♂️ 71 | >* 感谢每一个教我写代码和教我挖因子的人💐 72 | >* 欢迎交流技术优化&因子灵感&工作信息: 73 | 74 | #### 相关链接🔗 75 | >* [PyPi](https://pypi.org/project/pure-ocean-breeze/) 76 | >* [Github同步到Pypi操作手册](https://github.com/chen-001/pure_ocean_breeze/blob/master/Github同步Pypi操作手册/Github同步Pypi操作手册.md) 77 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/function.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if config.show_if_no_docstring or function.has_contents %} 3 | 4 |
5 | {% with html_id = function.path %} 6 | 7 | {% if not root or config.show_root_heading %} 8 | 9 | {% if root %} 10 | {% set show_full_path = config.show_root_full_path %} 11 | {% set root_members = True %} 12 | {% elif root_members %} 13 | {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} 14 | {% set root_members = False %} 15 | {% else %} 16 | {% set show_full_path = config.show_object_full_path %} 17 | {% endif %} 18 | 19 | {% filter heading(heading_level, 20 | id=html_id, 21 | class="doc doc-heading", 22 | toc_label=function.name ~ "()") %} 23 | 24 | {% filter highlight(language="python", inline=True) %} 25 | {% if show_full_path %}{{ function.path }}{% else %}{{ function.name }}{% endif %} 26 | {% with signature = function.signature %}{% include "signature.html" with context %}{% endwith %} 27 | {% endfilter %} 28 | 29 | {% with properties = function.properties %} 30 | {% include "properties.html" with context %} 31 | {% endwith %} 32 | 33 | {% endfilter %} 34 | 35 | {% else %} 36 | {% if config.show_root_toc_entry %} 37 | {% filter heading(heading_level, 38 | id=html_id, 39 | toc_label=function.path, 40 | hidden=True) %} 41 | {% endfilter %} 42 | {% endif %} 43 | {% set heading_level = heading_level - 1 %} 44 | {% endif %} 45 | 46 |
47 | {% with docstring_sections = function.docstring_sections %} 48 | {% include "docstring.html" with context %} 49 | {% endwith %} 50 | 51 | {% if config.show_source and function.source %} 52 |
53 | Source code in {{ function.relative_file_path }} 54 | {{ function.source.code|highlight(language="python", linestart=function.source.line_start, linenums=False) }} 55 |
56 | {% endif %} 57 |
58 | 59 | {% endwith %} 60 |
61 | 62 | {% endif %} 63 | -------------------------------------------------------------------------------- /Github同步Pypi操作手册/Github同步Pypi操作手册.md: -------------------------------------------------------------------------------- 1 | ### Github同步Pypi操作手册 2 | --- 3 | * #### 步骤一 ➡️ 创建workflow 4 | >1. 在**repository**主页找到**Actions** 5 | >![Acitons](Actions.jpg) 6 | >2. 在**Actions**中找到**Publish Python Package** 7 | >![Publish_python_package](Publish_python_package.jpg) 8 | >3. 点击**Configure**创建**workflow** 9 | >![workflow](workflow.jpg) 10 | >4. 此时自动在主分支下创建了`.github/workflows/python-publish.yml`文件 11 | > 12 | >👏**至此第一步结束啦👏** 13 | 14 | * #### 步骤二 ➡️ 设置自动更新动作 15 | >1. **解释**:`python-publish.yml`中的内容即更新动作 16 | >2. 复制如下模板,替换`python-publish.yml`中的内容 17 | >>```yml 18 | >># action的名称 19 | >>name: Upload Python Package 20 | >> 21 | >>on: 22 | >> # 当setup.py分支有push时,触发action 23 | >> push: 24 | >> paths: 25 | >> - 'setup.py' 26 | >> 27 | >>jobs: 28 | >> deploy: 29 | >> runs-on: ubuntu-latest 30 | >> steps: 31 | >> - uses: actions/checkout@v3 32 | >> - name: Set up Python 33 | >> uses: actions/setup-python@v4 34 | >> with: 35 | >> python-version: '3.x' 36 | >> - name: Install dependencies 37 | >> run: | 38 | >> python -m pip install --upgrade pip 39 | >> pip install build 40 | >> - name: Build package 41 | >> run: python -m build 42 | >> - name: Publish package 43 | >> uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 44 | >> with: 45 | >> user: ${{secrets.PYPI_USERNAME}} 46 | >> password: ${{secrets.PYPI_PASSWORD}} 47 | >>``` 48 | >3. 提交创建`python-publish.yml`文件 49 | >![start-commit](start_commit.jpg) 50 | > 51 | >👏**至此第二步结束啦👏** 52 | 53 | * #### 步骤三 ➡️ 设置Pypi账号密码 54 | >1. **解释**:为了不在`python-publish.yml`文件中暴露自己Pypi的账号密码,需要在Github的仓库密钥中设置自己的账号密码。对应在上述`python-publish.yml`文件中的 55 | >>```yml 56 | >>user: ${{secrets.PYPI_USERNAME}} 57 | >>password: ${{secrets.PYPI_PASSWORD}} 58 | >>``` 59 | >2. 打开**repository**的**settings**(⚠️注意,并非个人账户的settings) 60 | >![settings](settings.jpg) 61 | >3. 选择左侧**Secrets**下的**Actions**,然后点击右上角的**New repository secret** 62 | >![secrets](secrets.jpg) 63 | >4. 添加新secret,pypi的账号,在Name中输入**PYPI_USERNAME**,在Value中输入自己Pypi账号 64 | >![add_username](add_username.jpg) 65 | >5. 再添加一个secret,pypi的密码,在Name中输入**PYPI_PASSWORD**,在Value中输入自己的Pypi密码 66 | >![secrets_again](secrets_again.jpg) 67 | >![add_password](add_password.jpg) 68 | > 69 | >👏**至此第三步结束啦👏** 70 | 71 | * #### 步骤四 ➡️ 上传`setup.py`文件 72 | >1. 上传`setup.py`文件 73 | >![setup_py](setup_py.jpg) 74 | > 75 | >👏**至此第四步结束啦👏** 76 | 77 | * #### 使用说明 78 | >1. 更新Pypi新版本:更新`setup.py`文件并push,会触发前面设置的Action,从而自动更新Pypi新版本 79 | >2. 其他文件的更新:只要不包含`setup.py`的更新,就不会触发更新Pypi版本的Action -------------------------------------------------------------------------------- /更新日志/version4.md: -------------------------------------------------------------------------------- 1 | ## 更新日志🗓 — v4 2 | 3 | 4 | * v4.0.8 — 2023.10.14 5 | 6 | > 1. 修复了Questdb读取到None时不能识别的bug 7 | > 2. 修正了read_daily计算振幅的公式错误 8 | > 3. 修复了read_market中读取开盘价时的错误 9 | > 4. 升级了pure_moon因子回测结果展示,删去了因子截面标准差,新增了各组月均超额收益项 10 | > 5. 给follow_tests新增了groups_num参数,用于指定分组数量;新增了without_industry参数,指定不在行业成分股内做测试 11 | 12 | 13 | * v4.0.7 — 2023.7.17 14 | 15 | > 1. 给pure_fall_frequent和pure_fall_nature新增了use_mpire参数,可以使用mpire库开启并行 16 | > 2. 修复了pure_fall_frequent的select_one_calculate方法中,可能存在的返回类型为字符串的问题 17 | > 3. 修复了pure_fall_nature中全新因子运算完拼接时的bug 18 | 19 | 20 | * v4.0.6 — 2023.7.14 21 | 22 | > 1. 修复了pure_dawn读取已有因子值时`__call__`方法返回错误的问题 23 | > 2. 修复了pure_fall_frequent中关于计算因子值的bug 24 | > 3. 修复了optimize_many_days函数和pure_linprog的run方法不受STATES['START']影响的bug 25 | > 4. 随着时间的推移,将STATES['START']值修改为20140101 26 | > 5. 更新了依赖库 27 | 28 | 29 | * v4.0.5 — 2023.7.7 30 | 31 | > 1. 修复了pure_fall_nature中缺少fields参数和参数传递的bug 32 | > 2. 修复了pure_fall_nature中拼接新旧因子时的错误 33 | > 3. 删去了pure_fall_nature中get_daily_factors的系统通知 34 | > 4. 将pure_fall_frequent中的并行改为使用concurrent 35 | > 5. 对pure_fall_frequent中的get_daily_factors新增了防止系统通知报错的功能 36 | > 6. 对pure_coldwinter加入了缓存机制,改进了相关系数的计算方法,大幅提高了运算速度 37 | > 7. 对pure_coldwinter剔除的风格因子默认新增了传统反转因子、传统波动因子、传统换手因子 38 | > 8. 将get_abs的median参数替换为quantile参数,表示计算到截面某个分位点的距离 39 | > 9. 将clip_mad中的参数n默认值改为5 40 | 41 | 42 | * v4.0.4 — 2023.7.4 43 | 44 | > 1. 优化了show_corrs_with_old的结果展示排序 45 | > 2. 修复了pure_fall_nature中关于money与amount的错误 46 | > 3. 给pure_fall_nature的get_daily_factors方法增加了fields参数,用于指定要读取的字段,以节约内存(使用duckdb实现) 47 | > 4. 将pure_fall_nature中的并行方法改为使用concurrent 48 | 49 | 50 | * v4.0.3 — 2023.7.4 51 | 52 | > 1. 新增了全新的因子成果数据库FactorDone,每个最终复合因子,都附带其细分因子 53 | > 2. 对show_corr函数的默认计算方式进行了提速,并新增old_way参数,用于使用旧版方式计算 54 | > 3. 将show_corr、show_corrs、show_corrs_with_old函数中相关系数的默认计算方式调整为pearson相关系数 55 | > 4. 对show_corrs_with_old的内容进行了升级,以支持计算因子与已有因子的细分因子之间的相关系数 56 | 57 | 58 | * v4.0.2 — 2023.7.3 59 | 60 | > 1. 暂时取消了对numpy的强制依赖 61 | 62 | 63 | * v4.0.1 — 2023.7.3 64 | 65 | > 1. 修正了convert_tick_by_tick_data_to_parquet中成交额与成交量的混淆错误 66 | > 2. 修复了database_update_zxindustry_member中的一些异常 67 | > 3. 修复了pure_fall_nature中的一些异常 68 | > 4. 取消了对matplotlib库的依赖 69 | 70 | 71 | * v4.0.0 — 2023.6.28 72 | 73 | > 1. 修复了初始化时可能产生的报错,将初始化函数更名为ini,可通过如下语句初始化 74 | > 75 | > ```python 76 | > import pure_ocean_breeze as p 77 | > 78 | > p.ini() 79 | > ``` 80 | > 2. 初始化函数与`Homeplace`参数新增了存储逐笔数据的路径 81 | > 3. 增加了使用逐笔数据计算因子值的代码框架`pure_fall_nature`,并可以使用逐笔数据合成任意秒线、分钟线、小时线 82 | > 4. write_data部分新增了`convert_tick_by_tick_data_to_parquet`、`convert_tick_by_tick_data_daily`、`convert_tick_by_tick_data_monthly`三个更新逐笔数据的函数 83 | > 5. 修复了database_update_daily_files中关于更新pe_ttm数据的错误 84 | > 6. 修复了pure_moon中截止日期非最新日期时,关于多头超均收益的错误 85 | > 7. 修复了pure_fall_frequent中确实日期仅为异常日期时的bug 86 | > 8. ClickHouseClient中新增了秒级数据的show_all_dates 87 | > 9. 将对pandas的依赖限制在了1.5.3及以下版本 -------------------------------------------------------------------------------- /database/on_mysql/ricequant_to_mysql.py: -------------------------------------------------------------------------------- 1 | from pure_ocean_breeze.pure_ocean_breeze import * 2 | 3 | '''执行本代码之前,请先删除之前的两个数据minute_data和minute_data_alter''' 4 | 5 | #分钟数据文件所在的路径 6 | mipath='/Users/chenzongwei/pythoncode/数据库/Ricequant-minbar/equities' 7 | 8 | #新建数据库 9 | sql=sqlConfig() 10 | #每只股票一张表 11 | sql.add_new_database('minute_data_stock') 12 | #每天所有股票一张表 13 | sql.add_new_database('minute_data_stock_alter') 14 | #每个指数一张表 15 | sql.add_new_database('minute_data_index') 16 | #每天所有指数一张表 17 | sql.add_new_database('minute_data_index_alter') 18 | 19 | #连接4个数据库 20 | sqls=sqlConfig('minute_data_stock') 21 | sqlsa=sqlConfig('minute_data_stock_alter') 22 | sqli=sqlConfig('minute_data_index') 23 | sqlia=sqlConfig('minute_data_index_alter') 24 | 25 | #遍历所有分钟数据文件 26 | files=os.listdir(mipath) 27 | files=[mipath+'/'+i for i in files] 28 | fails=[] 29 | 30 | for path in tqdm.tqdm(files): 31 | #识别代码属于股票还是指数 32 | code,kind=convert_code(path) 33 | #一些数据预处理 34 | df=read_h5_new(path) 35 | df=df.rename(columns={'datetime':'date','volume':'amount','total_turnover':'money'}) 36 | df=df[['date','open','high','low','close','amount','money']].sort_values('date') 37 | df.date=df.date.astype(str).str.slice(stop=8).astype(int) 38 | df=df.groupby('date').apply(lambda x:x.assign(num=list(range(1,x.shape[0]+1)))) 39 | df=(np.around(df,2)*100).ffill().dropna().astype(int).assign(code=code) 40 | #所有的日期 41 | dates=list(set(df.date)) 42 | try: 43 | #股票 44 | if kind=='stock': 45 | #写入每只股票一张表 46 | df.drop(columns=['code']).to_sql( 47 | name=code,con=sqls.engine,if_exists='replace',index=False, 48 | dtype={'date':INT,'open':INT,'high':INT,'low':INT,'close':INT,'amount':BIGINT,'money':BIGINT,'num':INT}) 49 | #把每天写入每天所有股票一张表 50 | for date in dates: 51 | dfi=df[df.date==date] 52 | dfi.drop(columns=['date']).to_sql( 53 | name=str(date),con=sqlsa.engine,if_exists='append',index=False, 54 | dtype={'code':VARCHAR(9),'open':INT,'high':INT,'low':INT,'close':INT,'amount':BIGINT,'money':BIGINT,'num':INT}) 55 | #指数 56 | else: 57 | #写入每个指数一张表 58 | df.drop(columns=['code']).to_sql( 59 | name=code,con=sqli.engine,if_exists='replace',index=False, 60 | dtype={'date':INT,'open':INT,'high':INT,'low':INT,'close':INT,'amount':BIGINT,'money':BIGINT,'num':INT}) 61 | #把每天写入每天所有指数一张表 62 | for date in dates: 63 | dfi=df[df.date==date] 64 | dfi.drop(columns=['date']).to_sql( 65 | name=str(date),con=sqlia.engine,if_exists='append',index=False, 66 | dtype={'code':VARCHAR(9),'open':INT,'high':INT,'low':INT,'close':INT,'amount':BIGINT,'money':BIGINT,'num':INT}) 67 | except Exception: 68 | fails.append(code) 69 | logger.warning(f'{code}失败了,请检查') 70 | logger.success('全部搬运到mysql里啦') -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/data/dicts.py: -------------------------------------------------------------------------------- 1 | __updated__ = "2023-10-14 15:32:40" 2 | 3 | # 申万一级行业的代码与名字对照表 4 | INDUS_DICT = { 5 | k: v 6 | for k, v in zip( 7 | [ 8 | "801170.SI", 9 | "801010.SI", 10 | "801140.SI", 11 | "801080.SI", 12 | "801780.SI", 13 | "801110.SI", 14 | "801230.SI", 15 | "801950.SI", 16 | "801180.SI", 17 | "801040.SI", 18 | "801740.SI", 19 | "801890.SI", 20 | "801770.SI", 21 | "801960.SI", 22 | "801200.SI", 23 | "801120.SI", 24 | "801710.SI", 25 | "801720.SI", 26 | "801880.SI", 27 | "801750.SI", 28 | "801050.SI", 29 | "801790.SI", 30 | "801150.SI", 31 | "801980.SI", 32 | "801030.SI", 33 | "801730.SI", 34 | "801160.SI", 35 | "801130.SI", 36 | "801210.SI", 37 | "801970.SI", 38 | "801760.SI", 39 | ], 40 | [ 41 | "交通运输", 42 | "农林牧渔", 43 | "轻工制造", 44 | "电子", 45 | "银行", 46 | "家用电器", 47 | "综合", 48 | "煤炭", 49 | "房地产", 50 | "钢铁", 51 | "国防军工", 52 | "机械设备", 53 | "通信", 54 | "石油石化", 55 | "商贸零售", 56 | "食品饮料", 57 | "建筑材料", 58 | "建筑装饰", 59 | "汽车", 60 | "计算机", 61 | "有色金属", 62 | "非银金融", 63 | "医药生物", 64 | "美容护理", 65 | "基础化工", 66 | "电力设备", 67 | "公用事业", 68 | "纺织服饰", 69 | "社会服务", 70 | "环保", 71 | "传媒", 72 | ], 73 | ) 74 | } 75 | 76 | # 常用宽基指数的代码和名字 77 | INDEX_DICT = { 78 | "000300.SH": "沪深300", 79 | "000905.SH": "中证500", 80 | "000852.SH": "中证1000", 81 | "399303.SZ": "国证2000", 82 | } 83 | 84 | # 中信一级行业代码和名字对照表 85 | ZXINDUS_DICT = { 86 | "CI005001.INDX": "石油石化", 87 | "CI005002.INDX": "煤炭", 88 | "CI005003.INDX": "有色金属", 89 | "CI005004.INDX": "电力及公用事业", 90 | "CI005005.INDX": "电力及公用事业", 91 | "CI005005.INDX": "钢铁", 92 | "CI005006.INDX": "基础化工", 93 | "CI005007.INDX": "建筑", 94 | "CI005008.INDX": "建材", 95 | "CI005009.INDX": "轻工制造", 96 | "CI005010.INDX": "机械", 97 | "CI005011.INDX": "电力设备及新能源", 98 | "CI005012.INDX": "国防军工", 99 | "CI005013.INDX": "汽车", 100 | "CI005014.INDX": "商贸零售", 101 | "CI005015.INDX": "消费者服务", 102 | "CI005016.INDX": "家电", 103 | "CI005017.INDX": "纺织服装", 104 | "CI005018.INDX": "医药", 105 | "CI005019.INDX": "食品饮料", 106 | "CI005020.INDX": "农林牧渔", 107 | "CI005021.INDX": "银行", 108 | "CI005022.INDX": "非银行金融", 109 | "CI005023.INDX": "房地产", 110 | "CI005024.INDX": "交通运输", 111 | "CI005025.INDX": "电子", 112 | "CI005026.INDX": "通信", 113 | "CI005027.INDX": "计算机", 114 | "CI005028.INDX": "传媒", 115 | "CI005029.INDX": "综合", 116 | "CI005030.INDX": "综合金融", 117 | } 118 | -------------------------------------------------------------------------------- /docs/罗盘/数据/dicts.md: -------------------------------------------------------------------------------- 1 | ::: pure_ocean_breeze.data.dicts 2 | ## 申万一级行业的代码与名字对照表 3 | ```python 4 | INDUS_DICT = { 5 | k: v 6 | for k, v in zip( 7 | [ 8 | "801170.SI", 9 | "801010.SI", 10 | "801140.SI", 11 | "801080.SI", 12 | "801780.SI", 13 | "801110.SI", 14 | "801230.SI", 15 | "801950.SI", 16 | "801180.SI", 17 | "801040.SI", 18 | "801740.SI", 19 | "801890.SI", 20 | "801770.SI", 21 | "801960.SI", 22 | "801200.SI", 23 | "801120.SI", 24 | "801710.SI", 25 | "801720.SI", 26 | "801880.SI", 27 | "801750.SI", 28 | "801050.SI", 29 | "801790.SI", 30 | "801150.SI", 31 | "801980.SI", 32 | "801030.SI", 33 | "801730.SI", 34 | "801160.SI", 35 | "801130.SI", 36 | "801210.SI", 37 | "801970.SI", 38 | "801760.SI", 39 | ], 40 | [ 41 | "交通运输", 42 | "农林牧渔", 43 | "轻工制造", 44 | "电子", 45 | "银行", 46 | "家用电器", 47 | "综合", 48 | "煤炭", 49 | "房地产", 50 | "钢铁", 51 | "国防军工", 52 | "机械设备", 53 | "通信", 54 | "石油石化", 55 | "商贸零售", 56 | "食品饮料", 57 | "建筑材料", 58 | "建筑装饰", 59 | "汽车", 60 | "计算机", 61 | "有色金属", 62 | "非银金融", 63 | "医药生物", 64 | "美容护理", 65 | "基础化工", 66 | "电力设备", 67 | "公用事业", 68 | "纺织服饰", 69 | "社会服务", 70 | "环保", 71 | "传媒", 72 | ], 73 | ) 74 | } 75 | ``` 76 | 77 | ## 常用宽基指数的代码和名字 78 | ```python 79 | INDEX_DICT = { 80 | "000300.SH": "沪深300", 81 | "000905.SH": "中证500", 82 | "000852.SH": "中证1000", 83 | "399303.SZ": "国证2000" 84 | } 85 | ``` 86 | 87 | ## 中信一级行业代码和名字对照表 88 | ```python 89 | ZXINDUS_DICT = { 90 | "CI005001.INDX": "石油石化", 91 | "CI005002.INDX": "煤炭", 92 | "CI005003.INDX": "有色金属", 93 | "CI005004.INDX": "电力及公用事业", 94 | "CI005005.INDX": "电力及公用事业", 95 | "CI005005.INDX": "钢铁", 96 | "CI005006.INDX": "基础化工", 97 | "CI005007.INDX": "建筑", 98 | "CI005008.INDX": "建材", 99 | "CI005009.INDX": "轻工制造", 100 | "CI005010.INDX": "机械", 101 | "CI005011.INDX": "电力设备及新能源", 102 | "CI005012.INDX": "国防军工", 103 | "CI005013.INDX": "汽车", 104 | "CI005014.INDX": "商贸零售", 105 | "CI005015.INDX": "消费者服务", 106 | "CI005016.INDX": "家电", 107 | "CI005017.INDX": "纺织服装", 108 | "CI005018.INDX": "医药", 109 | "CI005019.INDX": "食品饮料", 110 | "CI005020.INDX": "农林牧渔", 111 | "CI005021.INDX": "银行", 112 | "CI005022.INDX": "非银行金融", 113 | "CI005023.INDX": "房地产", 114 | "CI005024.INDX": "交通运输", 115 | "CI005025.INDX": "电子", 116 | "CI005026.INDX": "通信", 117 | "CI005027.INDX": "计算机", 118 | "CI005028.INDX": "传媒", 119 | "CI005029.INDX": "综合", 120 | "CI005030.INDX": "综合金融", 121 | } 122 | ``` 123 | 124 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/templates/python/material/children.html: -------------------------------------------------------------------------------- 1 | {{ log.debug() }} 2 | {% if obj.children %} 3 | 4 |
5 | 6 | {% if config.group_by_category %} 7 | 8 | {% with %} 9 | 10 | {% if config.show_category_heading %} 11 | {% set extra_level = 1 %} 12 | {% else %} 13 | {% set extra_level = 0 %} 14 | {% endif %} 15 | 16 | {% if config.show_category_heading and obj.attributes|any("has_contents") %} 17 | {% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %} 18 | {% endif %} 19 | {% with heading_level = heading_level + extra_level %} 20 | {% for attribute in obj.attributes|sort(attribute="name") %} 21 | {% include "attribute.html" with context %} 22 | {% endfor %} 23 | {% endwith %} 24 | 25 | {% if config.show_category_heading and obj.classes|any("has_contents") %} 26 | {% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %} 27 | {% endif %} 28 | {% with heading_level = heading_level + extra_level %} 29 | {% for class in obj.classes|sort(attribute="name") %} 30 | {% include "class.html" with context %} 31 | {% endfor %} 32 | {% endwith %} 33 | 34 | {% if config.show_category_heading and obj.functions|any("has_contents") %} 35 | {% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %} 36 | {% endif %} 37 | {% with heading_level = heading_level + extra_level %} 38 | {% for function in obj.functions|sort(attribute="name") %} 39 | {% include "function.html" with context %} 40 | {% endfor %} 41 | {% endwith %} 42 | 43 | {% if config.show_category_heading and obj.methods|any("has_contents") %} 44 | {% filter heading(heading_level, id=html_id ~ "-methods") %}Methods{% endfilter %} 45 | {% endif %} 46 | {% with heading_level = heading_level + extra_level %} 47 | {% for method in obj.methods|sort(attribute="name") %} 48 | {% include "method.html" with context %} 49 | {% endfor %} 50 | {% endwith %} 51 | 52 | {% if config.show_category_heading and obj.modules|any("has_contents") %} 53 | {% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %} 54 | {% endif %} 55 | {% with heading_level = heading_level + extra_level %} 56 | {% for module in obj.modules|sort(attribute="name") %} 57 | {% include "module.html" with context %} 58 | {% endfor %} 59 | {% endwith %} 60 | 61 | {% endwith %} 62 | 63 | {% else %} 64 | 65 | {% for child in obj.children|sort(attribute="name") %} 66 | {% if child.category == "attribute" %} 67 | {% with attribute = child %} 68 | {% include "attribute.html" with context %} 69 | {% endwith %} 70 | 71 | {% elif child.category == "class" %} 72 | {% with class = child %} 73 | {% include "class.html" with context %} 74 | {% endwith %} 75 | 76 | {% elif child.category == "function" %} 77 | {% with function = child %} 78 | {% include "function.html" with context %} 79 | {% endwith %} 80 | 81 | {% elif child.category == "method" %} 82 | {% with method = child %} 83 | {% include "method.html" with context %} 84 | {% endwith %} 85 | 86 | {% elif child.category == "module" %} 87 | {% with module = child %} 88 | {% include "module.html" with context %} 89 | {% endwith %} 90 | 91 | {% endif %} 92 | 93 | {% endfor %} 94 | 95 | {% endif %} 96 | 97 |
98 | 99 | {% endif %} 100 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/loggers.py: -------------------------------------------------------------------------------- 1 | """Logging functions.""" 2 | 3 | import logging 4 | from pathlib import Path 5 | from typing import Callable, Optional 6 | 7 | from jinja2 import contextfunction 8 | from jinja2.runtime import Context 9 | from mkdocs.utils import warning_filter 10 | 11 | from mkdocstrings.handlers import base 12 | 13 | 14 | class LoggerAdapter(logging.LoggerAdapter): 15 | """A logger adapter to prefix messages.""" 16 | 17 | def __init__(self, prefix, logger): 18 | """ 19 | Initialize the object. 20 | 21 | Arguments: 22 | prefix: The string to insert in front of every message. 23 | logger: The logger instance. 24 | """ 25 | super().__init__(logger, {}) 26 | self.prefix = prefix 27 | 28 | def process(self, msg, kwargs): 29 | """ 30 | Process the message. 31 | 32 | Arguments: 33 | msg: The message: 34 | kwargs: Remaining arguments. 35 | 36 | Returns: 37 | The processed message. 38 | """ 39 | return f"{self.prefix}: {msg}", kwargs 40 | 41 | 42 | class TemplateLogger: 43 | """ 44 | A wrapper class to allow logging in templates. 45 | 46 | Attributes: 47 | debug: Function to log a DEBUG message. 48 | info: Function to log an INFO message. 49 | warning: Function to log a WARNING message. 50 | error: Function to log an ERROR message. 51 | critical: Function to log a CRITICAL message. 52 | """ 53 | 54 | def __init__(self, logger: LoggerAdapter): 55 | """ 56 | Initialize the object. 57 | 58 | Arguments: 59 | logger: A logger adapter. 60 | """ 61 | self.debug = get_template_logger_function(logger.debug) 62 | self.info = get_template_logger_function(logger.info) 63 | self.warning = get_template_logger_function(logger.warning) 64 | self.error = get_template_logger_function(logger.error) 65 | self.critical = get_template_logger_function(logger.critical) 66 | 67 | 68 | def get_template_logger_function(logger_func: Callable) -> Callable: 69 | """ 70 | Create a wrapper function that automatically receives the Jinja template context. 71 | 72 | Arguments: 73 | logger_func: The logger function to use within the wrapper. 74 | 75 | Returns: 76 | A function. 77 | """ 78 | 79 | @contextfunction 80 | def wrapper(context: Context, msg: Optional[str] = None) -> str: 81 | """ 82 | Log a message. 83 | 84 | Arguments: 85 | context: The template context, automatically provided by Jinja. 86 | msg: The message to log. 87 | 88 | Returns: 89 | An empty string. 90 | """ 91 | template_path = get_template_path(context) 92 | logger_func(f"{template_path}: {msg or 'Rendering'}") 93 | return "" 94 | 95 | return wrapper 96 | 97 | 98 | def get_template_path(context: Context) -> str: 99 | """ 100 | Return the path to the template currently using the given context. 101 | 102 | Arguments: 103 | context: The template context. 104 | 105 | Returns: 106 | The relative path to the template. 107 | """ 108 | filename = context.environment.get_template(context.name).filename 109 | if filename: 110 | try: 111 | return str(Path(filename).relative_to(base.TEMPLATES_DIR)) 112 | except ValueError: 113 | return filename 114 | return context.name 115 | 116 | 117 | def get_logger(name: str) -> LoggerAdapter: 118 | """ 119 | Return a pre-configured logger. 120 | 121 | Arguments: 122 | name: The name to use with `logging.getLogger`. 123 | 124 | Returns: 125 | A logger configured to work well in MkDocs. 126 | """ 127 | logger = logging.getLogger(f"mkdocs.plugins.{name}") 128 | logger.addFilter(warning_filter) 129 | return LoggerAdapter(name, logger) 130 | 131 | 132 | def get_template_logger() -> TemplateLogger: 133 | """ 134 | Return a logger usable in templates. 135 | 136 | Returns: 137 | A template logger. 138 | """ 139 | return TemplateLogger(get_logger("mkdocstrings.templates")) 140 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: pure_ocean_breeze documentation 2 | site_url: https://chen-001.github.io/pure_ocean_breeze/ 3 | repo_url: https://github.com/chen-001/pure_ocean_breeze 4 | site_description: 关于量价因子的一片星空 5 | site_author: chenzongwei 6 | 7 | nav: 8 | - 星空: index.md 9 | 10 | - 罗盘: 11 | 12 | - 初始化: 13 | - initialize: 罗盘/初始化/initialize.md 14 | - 配置&参数: 15 | - state: 罗盘/配置&参数/state.md 16 | - homeplace: 罗盘/配置&参数/homeplace.md 17 | - 数据: 18 | - database: 罗盘/数据/database.md 19 | - dicts: 罗盘/数据/dicts.md 20 | - read_data: 罗盘/数据/read_data.md 21 | - write_data: 罗盘/数据/write_data.md 22 | - tools: 罗盘/数据/tools.md 23 | - 加工&测试&评价: 24 | - process: 罗盘/加工&测试&评价/process.md 25 | - comment: 罗盘/加工&测试&评价/comment.md 26 | - 通讯: 27 | - mail: 罗盘/通讯/mail.md 28 | 29 | - 火把: 30 | - 项目: 火把/project.md 31 | - 构想&未来: 火把/futures.md 32 | - 分享: 火把/shares.md 33 | - 作者: 火把/author.md 34 | 35 | # theme: 36 | # name: material 37 | # language: 'zh' 38 | # # locale: zh_CN 39 | # analytics: {gtag: 'G-274394082'} 40 | # markdown_extensions: 41 | # - pymdownx.arithmatex: 42 | # generic: true 43 | extra_javascript: 44 | - https://polyfill.io/v3/polyfill.min.js?features=es6 45 | - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 46 | - https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@3.0.8/js/maodian.js 47 | - js/baidu.js 48 | - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML 49 | 50 | 51 | # extra: 52 | # search: 53 | # language: 'jp' 54 | 55 | plugins: 56 | - autorefs 57 | - mkdocstrings: 58 | handlers: 59 | python: 60 | setup_commands: 61 | - import sys 62 | - sys.path.append("docs") 63 | selection: 64 | new_path_syntax: yes 65 | watch: 66 | - docs/src/mkdocstrings 67 | - search: 68 | lang: 69 | - en 70 | - ja 71 | separator: '[\s\-\.]+' 72 | 73 | extra_css: 74 | - css/mkdocstrings.css 75 | 76 | theme: 77 | name: material 78 | # custom_dir: overrides 79 | logo: pics/一身本事.png 80 | palette: 81 | - scheme: default # 日间模式 82 | primary: indigo # 上方的 83 | accent: red # 链接等可交互元件的高亮色 84 | toggle: 85 | icon: material/weather-night # 图标 86 | name: 切换至夜间模式 # 鼠标悬浮提示 87 | - scheme: slate # 夜间模式 88 | primary: teal 89 | accent: red 90 | toggle: 91 | icon: material/weather-sunny 92 | name: 切换至日间模式 93 | features: 94 | - content.code.annotate # 代码块内的注释 95 | - navigation.tabs # 使用Tab来进行分类 96 | - navigation.top # 返回顶部的按钮 在上滑时出现 97 | - navigation.indexes # Tab会有一个index.md 而不是在打开Tab时打开第一篇文章 98 | - navigation.expand # 打开Tab时左侧目录全部展开 99 | - search.suggest # 搜索输入一些字母时推荐补全整个单词 100 | - search.highlight # 搜索出的文章关键词加入高亮 101 | language: zh # 一些提示性的文字会变成中文 102 | # icon: 103 | # repo: fontawesome/brands/github 104 | 105 | edit_uri: "" # 编辑按钮跳转的链接 106 | 107 | copyright: Copyright © 2022 chenzongwei # 左下角的版权声明 108 | 109 | extra: 110 | analytics: 111 | provider: google 112 | property: G-8R9VRSN3S0 113 | social: # icons 114 | - icon: fontawesome/brands/github 115 | link: https://github.com/chen-001/pure_ocean_breeze 116 | name: GitHub项目主页 117 | - icon: fontawesome/brands/python 118 | link: https://pypi.org/project/pure-ocean-breeze/ 119 | name: Pypi项目主页 120 | search: 121 | language: 'jp' 122 | 123 | 124 | 125 | markdown_extensions: 126 | - md_in_html 127 | - attr_list # 给图片后面添加{width="300"}设置大小 128 | - pymdownx.arithmatex: # latex支持 129 | generic: true 130 | - footnotes 131 | - toc: 132 | permalink: true # 固定标题位置为当前位置 133 | toc_depth: 3 134 | - pymdownx.highlight: # 代码块高亮 135 | linenums: true # 显示行号 136 | auto_title: true # 显示编程语言名称 137 | - admonition 138 | - pymdownx.details 139 | - pymdownx.superfences: 140 | custom_fences: 141 | - name: mermaid 142 | class: mermaid 143 | # format: !!python/name:pymdownx.superfences.fence_code_format 144 | - pymdownx.tabbed: 145 | alternate_style: true 146 | - meta # 支持Markdown文件上方自定义标题标签等 147 | 148 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/data/database.py: -------------------------------------------------------------------------------- 1 | __updated__ = "2025-02-26 15:30:49" 2 | 3 | import pandas as pd 4 | import psycopg2 as pg 5 | import numpy as np 6 | from typing import Union 7 | from tenacity import retry, stop_after_attempt, wait_fixed 8 | import questdb.ingress as qdbing 9 | 10 | 11 | class Questdb(object): 12 | """Questdb数据库连接和操作类 13 | 14 | Questdb的写入方式都为追加,因此如果想replace之前的数据,请手动删除表格 15 | Questdb的web console为127.0.0.1:9000,作者已经修改为127.0.0.1:9001 16 | """ 17 | 18 | def __init__( 19 | self, 20 | user: str = "admin", 21 | password: str = "quest", 22 | host: str = "192.168.200.60", 23 | port: str = "8812", 24 | database: str = "qdb", 25 | ) -> None: 26 | """初始化Questdb连接参数 27 | 28 | Parameters 29 | ---------- 30 | user : str, optional 31 | 用户名, by default "admin" 32 | password : str, optional 33 | 密码, by default "quest" 34 | host : str, optional 35 | 地址, by default "192.168.200.60" 36 | port : str, optional 37 | 端口, by default "8812" 38 | database : str, optional 39 | 数据库, by default "qdb" 40 | web_port : str, optional 41 | questdb控制台的端口号,安装questdb软件时默认为9000,本库默认为9000, by default 9000 42 | """ 43 | self.user = user 44 | self.password = password 45 | self.host = host 46 | self.port = port 47 | self.database = database 48 | 49 | def connect(self): 50 | """连接数据库 51 | 52 | Returns 53 | ------- 54 | connection 55 | 数据库连接 56 | """ 57 | conn = pg.connect( 58 | user=self.user, 59 | password=self.password, 60 | host=self.host, 61 | port=self.port, 62 | database=self.database, 63 | ) 64 | return conn 65 | 66 | def do_order(self, sql_order: str) -> any: 67 | """执行任意一句sql语句 68 | 69 | Parameters 70 | ---------- 71 | sql_order : str 72 | sql命令 73 | 74 | Returns 75 | ------- 76 | any 77 | 返回结果 78 | """ 79 | conn = self.connect() 80 | cur = conn.cursor() 81 | return cur.execute(sql_order) 82 | 83 | @retry(stop=stop_after_attempt(10)) 84 | def get_data( 85 | self, sql_order: str, only_array: bool = 0 86 | ) -> Union[pd.DataFrame, np.ndarray]: 87 | """以sql命令的方式,从数据库中读取数据 88 | 89 | Parameters 90 | ---------- 91 | sql_order : str 92 | sql命令 93 | only_array : bool, optional 94 | 是否只返回数组, by default 0 95 | 96 | Returns 97 | ------- 98 | Union[pd.DataFrame, np.ndarray] 99 | 读取的结果 100 | """ 101 | conn = self.connect() 102 | cursor = conn.cursor() 103 | cursor.execute(sql_order) 104 | df_data = cursor.fetchall() 105 | if not only_array: 106 | columns = [i[0] for i in cursor.description] 107 | df = pd.DataFrame(df_data, columns=columns) 108 | return df 109 | else: 110 | return np.array(df_data) 111 | 112 | @retry(stop=stop_after_attempt(3000), wait=wait_fixed(0.01)) 113 | def write_via_df( 114 | self, 115 | df: pd.DataFrame, 116 | table_name: str, 117 | symbols: Union[str, bool, list[int], list[str]] = None, 118 | tuple_col: Union[str, list[str]] = None, 119 | ) -> None: 120 | """通过questdb的python库直接将dataframe写入quested数据库 121 | 122 | Parameters 123 | ---------- 124 | df : pd.DataFrame 125 | 要写入的dataframe 126 | table_name : str 127 | questdb中该表的表名 128 | symbols : Union[str, bool, list[int], list[str]], optional 129 | 为symbols的那些列的名称, by default None 130 | tuple_col : Union[str, list[str]], optional 131 | 数据类型为tuple或list的列的名字, by default None 132 | """ 133 | if tuple_col is None: 134 | ... 135 | elif isinstance(tuple_col, str): 136 | df[tuple_col] = df[tuple_col].apply(str) 137 | else: 138 | for t in tuple_col: 139 | df[t] = df[t].apply(str) 140 | 141 | if symbols is not None: 142 | with qdbing.Sender(self.host, 9009) as sender: 143 | sender.dataframe(df, table_name=table_name, symbols=symbols) 144 | else: 145 | with qdbing.Sender(self.host, 9009) as sender: 146 | sender.dataframe(df, table_name=table_name) 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/labor/comment.py: -------------------------------------------------------------------------------- 1 | __updated__ = "2025-06-25 20:30:28" 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import matplotlib as mpl 6 | 7 | mpl.rcParams.update(mpl.rcParamsDefault) 8 | import matplotlib.pyplot as plt 9 | 10 | plt.rcParams["axes.unicode_minus"] = False 11 | from typing import Tuple 12 | from pure_ocean_breeze.jason.data.read_data import ( 13 | read_index, 14 | ) 15 | from pure_ocean_breeze.jason.state.decorators import do_on_dfs 16 | from pure_ocean_breeze.jason.state.states import STATES 17 | 18 | 19 | 20 | def comments_on_twins( 21 | nets: pd.Series, rets: pd.Series, counts_one_year: int = 50 22 | ) -> pd.Series: 23 | """输入月频的收益率序列和净值序列,给出评价 24 | 评价指标包括年化收益率、总收益率、年化波动率、年化夏普比率、最大回撤率、胜率 25 | 输入2个pd.Series,时间是索引 26 | 27 | Parameters 28 | ---------- 29 | nets : pd.Series 30 | 净值序列,index为时间 31 | rets : pd.Series 32 | 收益率序列,index为时间 33 | counts_one_year : int 34 | 一年内有多少次交易, by default 50 35 | 36 | Returns 37 | ------- 38 | `pd.Series` 39 | 包含年化收益率、总收益率、年化波动率、年化夏普比率、最大回撤率、胜率的评价指标 40 | """ 41 | series = nets.copy() 42 | series1 = rets.copy() 43 | duration = (series.index[-1] - series.index[0]).days 44 | year = duration / 365 45 | ret_yearly = series.iloc[-1] / year 46 | max_draw = -((series+1) / (series+1).expanding(1).max()-1).min() 47 | vol = np.std(series1) * (counts_one_year**0.5) 48 | sharpe = ret_yearly / vol 49 | wins = series1[series1 > 0] 50 | win_rate = len(wins) / len(series1) 51 | return pd.Series( 52 | [series.iloc[-1], ret_yearly, vol, sharpe, win_rate, max_draw], 53 | index=["总收益率", "年化收益率", "年化波动率", "信息比率", "胜率", "最大回撤率"], 54 | ) 55 | 56 | 57 | def comments_on_twins_periods( 58 | nets: pd.Series, rets: pd.Series, periods: int 59 | ) -> pd.Series: 60 | """输入其他频率的收益率序列和净值序列,给出评价 61 | 评价指标包括年化收益率、总收益率、年化波动率、年化夏普比率、最大回撤率、胜率 62 | 输入2个pd.Series,时间是索引 63 | 64 | Parameters 65 | ---------- 66 | nets : pd.Series 67 | 净值序列,index为时间 68 | rets : pd.Series 69 | 收益率序列,index为时间 70 | periods : int 71 | 收益率序列的频率,如5天一次,则为5 72 | 73 | Returns 74 | ------- 75 | `pd.Series` 76 | 包含年化收益率、总收益率、年化波动率、年化夏普比率、最大回撤率、胜率的评价指标 77 | """ 78 | series = nets.copy() 79 | series1 = rets.copy() 80 | duration = (series.index[-1] - series.index[0]).days 81 | year = duration / 365 82 | ret_yearly = series.iloc[-1] / year 83 | max_draw = -((series+1) / (series+1).expanding(1).max()-1).min() 84 | vol = np.std(series1) * (252**0.5) * (periods**0.5) 85 | sharpe = ret_yearly / vol 86 | wins = series1[series1 > 0] 87 | win_rate = len(wins) / len(series1) 88 | return pd.Series( 89 | [series.iloc[-1], ret_yearly, vol, sharpe, win_rate, max_draw], 90 | index=["总收益率", "年化收益率", "年化波动率", "信息比率", "胜率", "最大回撤率"], 91 | ) 92 | 93 | 94 | @do_on_dfs 95 | def make_relative_comments( 96 | ret_fac: pd.Series, 97 | hs300: bool = 0, 98 | zz500: bool = 0, 99 | zz1000: bool = 0, 100 | day: int = STATES['START'], 101 | show_nets: bool = 0, 102 | ) -> pd.Series: 103 | """对于一个给定的收益率序列,计算其相对于某个指数的超额表现 104 | 105 | Parameters 106 | ---------- 107 | ret_fac : pd.Series 108 | 给定的收益率序列,index为时间 109 | hs300 : bool, optional 110 | 为1则相对沪深300指数行情, by default 0 111 | zz500 : bool, optional 112 | 为1则相对中证500指数行情, by default 0 113 | zz1000 : bool, optional 114 | 为1则相对中证1000指数行情, by default 0 115 | day : int, optional 116 | 起始日期,形如20130101, by default STATES['START'] 117 | show_nets : bool, optional 118 | 返回值中包括超额净值数据, by default 0 119 | 120 | Returns 121 | ------- 122 | `pd.Series` 123 | 评价指标包括年化收益率、总收益率、年化波动率、年化夏普比率、最大回撤率、胜率 124 | 125 | Raises 126 | ------ 127 | `IOError` 128 | 如果没指定任何一个指数,将报错 129 | """ 130 | 131 | if hs300: 132 | net_index = read_index(close=1,hs300=1,every_stock=0,start=day).resample("W").last() 133 | if zz500: 134 | net_index = read_index(close=1,zz500=1,every_stock=0,start=day).resample("W").last() 135 | if zz1000: 136 | net_index = read_index(close=1,zz1000=1,every_stock=0,start=day).resample("W").last() 137 | if (hs300 + zz500 + zz1000) == 0: 138 | raise IOError("你总得指定一个股票池吧?") 139 | ret_index = net_index.pct_change() 140 | if day is not None: 141 | ret_index = ret_index[ret_index.index >= pd.Timestamp(day)] 142 | ret = ret_fac - ret_index 143 | ret = ret.dropna() 144 | net = ret.cumsum() 145 | rtop = pd.Series(0, index=[net.index.min() - pd.DateOffset(weeks=1)]) 146 | net = pd.concat([rtop, net]).resample("W").last() 147 | ret = pd.concat([rtop, ret]).resample("W").last() 148 | com = comments_on_twins(net, ret) 149 | if show_nets: 150 | return com, net 151 | else: 152 | return com 153 | 154 | 155 | @do_on_dfs 156 | def make_relative_comments_plot( 157 | ret_fac: pd.Series, 158 | hs300: bool = 0, 159 | zz500: bool = 0, 160 | zz1000: bool = 0, 161 | day: int = STATES['START'], 162 | ) -> pd.Series: 163 | """对于一个给定的收益率序列,计算其相对于某个指数的超额表现,然后绘图,并返回超额净值序列 164 | 165 | Parameters 166 | ---------- 167 | ret_fac : pd.Series 168 | 给定的收益率序列,index为时间 169 | hs300 : bool, optional 170 | 为1则相对沪深300指数行情, by default 0 171 | zz500 : bool, optional 172 | 为1则相对中证500指数行情, by default 0 173 | zz1000 : bool, optional 174 | 为1则相对中证1000指数行情, by default 0 175 | day : int, optional 176 | 起始日期,形如20130101, by default STATES['START'] 177 | 178 | Returns 179 | ------- 180 | `pd.Series` 181 | 超额净值序列 182 | 183 | Raises 184 | ------ 185 | `IOError` 186 | 如果没指定任何一个指数,将报错 187 | """ 188 | if hs300: 189 | net_index = read_index(close=1,hs300=1,every_stock=0,start=day).resample("W").last() 190 | if zz500: 191 | net_index = read_index(close=1,zz500=1,every_stock=0,start=day).resample("W").last() 192 | if zz1000: 193 | net_index = read_index(close=1,zz1000=1,every_stock=0,start=day).resample("W").last() 194 | if (hs300 + zz500 + zz1000) == 0: 195 | raise IOError("你总得指定一个股票池吧?") 196 | ret_index = net_index.pct_change() 197 | if day is not None: 198 | ret_index = ret_index[ret_index.index >= pd.Timestamp(day)] 199 | ret = ret_fac - ret_index 200 | ret = ret.dropna() 201 | net = ret.cumsum() 202 | rtop = pd.Series(0, index=[net.index.min() - pd.DateOffset(weeks=1)]) 203 | net = pd.concat([rtop, net]).resample("W").last().ffill() 204 | net.iplot() 205 | return net 206 | -------------------------------------------------------------------------------- /docs/src/mkdocs_autorefs/references.py: -------------------------------------------------------------------------------- 1 | """Cross-references module.""" 2 | 3 | import re 4 | from html import escape, unescape 5 | from typing import Any, Callable, List, Match, Tuple, Union 6 | from xml.etree.ElementTree import Element 7 | 8 | from markdown import Markdown 9 | from markdown.extensions import Extension 10 | from markdown.inlinepatterns import REFERENCE_RE, ReferenceInlineProcessor 11 | 12 | AUTO_REF_RE = re.compile(r'[^"<>]*)\1>(?P.*?)</span>') 13 | """ 14 | A regular expression to match mkdocstrings' special reference markers 15 | in the [`on_post_page` hook][mkdocs_autorefs.plugin.AutorefsPlugin.on_post_page]. 16 | """ 17 | 18 | EvalIDType = Tuple[Any, Any, Any] 19 | 20 | 21 | class AutoRefInlineProcessor(ReferenceInlineProcessor): 22 | """A Markdown extension.""" 23 | 24 | def __init__(self, *args, **kwargs): # noqa: D107 25 | super().__init__(REFERENCE_RE, *args, **kwargs) 26 | 27 | # Code based on 28 | # https://github.com/Python-Markdown/markdown/blob/8e7528fa5c98bf4652deb13206d6e6241d61630b/markdown/inlinepatterns.py#L780 29 | 30 | def handleMatch(self, m, data) -> Union[Element, EvalIDType]: # noqa: N802 (parent's casing) 31 | """ 32 | Handle an element that matched. 33 | 34 | Arguments: 35 | m: The match object. 36 | data: The matched data. 37 | 38 | Returns: 39 | A new element or a tuple. 40 | """ 41 | text, index, handled = self.getText(data, m.end(0)) 42 | if not handled: 43 | return None, None, None 44 | 45 | identifier, end, handled = self.evalId(data, index, text) 46 | if not handled: 47 | return None, None, None 48 | 49 | if re.search(r"[/ \x00-\x1f]", identifier): # type: ignore 50 | # Do nothing if the matched reference contains: 51 | # - a space, slash or control character (considered unintended); 52 | # - specifically \x01 is used by Python-Markdown HTML stash when there's inline formatting, 53 | # but references with Markdown formatting are not possible anyway. 54 | return None, m.start(0), end 55 | 56 | return self.makeTag(identifier, text), m.start(0), end 57 | 58 | def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802 (parent's casing) 59 | """ 60 | Evaluate the id portion of `[ref][id]`. 61 | 62 | If `[ref][]` use `[ref]`. 63 | 64 | Arguments: 65 | data: The data to evaluate. 66 | index: The starting position. 67 | text: The text to use when no identifier. 68 | 69 | Returns: 70 | A tuple containing the identifier, its end position, and whether it matched. 71 | """ 72 | m = self.RE_LINK.match(data, pos=index) 73 | if not m: 74 | return None, index, False 75 | identifier = m.group(1) or text 76 | end = m.end(0) 77 | return identifier, end, True 78 | 79 | def makeTag(self, identifier: str, text: str) -> Element: # noqa: N802,W0221 (parent's casing, different params) 80 | """ 81 | Create a tag that can be matched by `AUTO_REF_RE`. 82 | 83 | Arguments: 84 | identifier: The identifier to use in the HTML property. 85 | text: The text to use in the HTML tag. 86 | 87 | Returns: 88 | A new element. 89 | """ 90 | el = Element("span") 91 | el.set("data-mkdocstrings-identifier", identifier) 92 | el.text = text 93 | return el 94 | 95 | 96 | def relative_url(url_a: str, url_b: str) -> str: 97 | """ 98 | Compute the relative path from URL A to URL B. 99 | 100 | Arguments: 101 | url_a: URL A. 102 | url_b: URL B. 103 | 104 | Returns: 105 | The relative URL to go from A to B. 106 | """ 107 | parts_a = url_a.split("/") 108 | url_b, anchor = url_b.split("#", 1) 109 | parts_b = url_b.split("/") 110 | 111 | # remove common left parts 112 | while parts_a and parts_b and parts_a[0] == parts_b[0]: 113 | parts_a.pop(0) 114 | parts_b.pop(0) 115 | 116 | # go up as many times as remaining a parts' depth 117 | levels = len(parts_a) - 1 118 | parts_relative = [".."] * levels + parts_b 119 | relative = "/".join(parts_relative) 120 | return f"{relative}#{anchor}" 121 | 122 | 123 | def fix_ref(url_mapper: Callable[[str], str], from_url: str, unmapped: List[str]) -> Callable: 124 | """ 125 | Return a `repl` function for [`re.sub`](https://docs.python.org/3/library/re.html#re.sub). 126 | 127 | In our context, we match Markdown references and replace them with HTML links. 128 | 129 | When the matched reference's identifier was not mapped to an URL, we append the identifier to the outer 130 | `unmapped` list. It generally means the user is trying to cross-reference an object that was not collected 131 | and rendered, making it impossible to link to it. We catch this exception in the caller to issue a warning. 132 | 133 | Arguments: 134 | url_mapper: A callable that gets an object's site URL by its identifier, 135 | such as [mkdocs_autorefs.plugin.AutorefsPlugin.get_item_url][]. 136 | from_url: The URL of the base page, from which we link towards the targeted pages. 137 | unmapped: A list to store unmapped identifiers. 138 | 139 | Returns: 140 | The actual function accepting a [`Match` object](https://docs.python.org/3/library/re.html#match-objects) 141 | and returning the replacement strings. 142 | """ 143 | 144 | def inner(match: Match): 145 | identifier = match["identifier"] 146 | title = match["title"] 147 | 148 | try: 149 | url = relative_url(from_url, url_mapper(unescape(identifier))) 150 | except KeyError: 151 | unmapped.append(identifier) 152 | if title == identifier: 153 | return f"[{identifier}][]" 154 | return f"[{title}][{identifier}]" 155 | 156 | return f'<a href="{escape(url)}">{title}</a>' 157 | 158 | return inner 159 | 160 | 161 | def fix_refs( 162 | html: str, 163 | from_url: str, 164 | url_mapper: Callable[[str], str], 165 | ) -> Tuple[str, List[str]]: 166 | """ 167 | Fix all references in the given HTML text. 168 | 169 | Arguments: 170 | html: The text to fix. 171 | from_url: The URL at which this HTML is served. 172 | url_mapper: A callable that gets an object's site URL by its identifier, 173 | such as [mkdocs_autorefs.plugin.AutorefsPlugin.get_item_url][]. 174 | 175 | Returns: 176 | The fixed HTML. 177 | """ 178 | unmapped = [] # type: ignore 179 | html = AUTO_REF_RE.sub(fix_ref(url_mapper, from_url, unmapped), html) 180 | return html, unmapped 181 | 182 | 183 | class AutorefsExtension(Extension): 184 | """Extension that inserts auto-references in Markdown.""" 185 | 186 | def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent method's name) 187 | """ 188 | Register the extension. 189 | 190 | Add an instance of our [`AutoRefInlineProcessor`][mkdocs_autorefs.references.AutoRefInlineProcessor] to the Markdown parser. 191 | 192 | Arguments: 193 | md: A `markdown.Markdown` instance. 194 | """ 195 | md.inlinePatterns.register( 196 | AutoRefInlineProcessor(md), 197 | "mkdocstrings", 198 | priority=168, # Right after markdown.inlinepatterns.ReferenceInlineProcessor 199 | ) 200 | -------------------------------------------------------------------------------- /docs/src/mkdocs_autorefs/plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the "mkdocs-autorefs" plugin. 3 | 4 | After each page is processed by the Markdown converter, this plugin stores absolute URLs of every HTML anchors 5 | it finds to later be able to fix unresolved references. 6 | It stores them during the [`on_page_content` event hook](https://www.mkdocs.org/user-guide/plugins/#on_page_content). 7 | 8 | Just before writing the final HTML to the disc, during the 9 | [`on_post_page` event hook](https://www.mkdocs.org/user-guide/plugins/#on_post_page), 10 | this plugin searches for references of the form `[identifier][]` or `[title][identifier]` that were not resolved, 11 | and fixes them using the previously stored identifier-URL mapping. 12 | """ 13 | 14 | import logging 15 | from typing import Callable, Dict, Optional 16 | 17 | from mkdocs.config import Config 18 | from mkdocs.plugins import BasePlugin 19 | from mkdocs.structure.pages import Page 20 | from mkdocs.structure.toc import AnchorLink 21 | from mkdocs.utils import warning_filter 22 | 23 | from mkdocs_autorefs.references import AutorefsExtension, fix_refs 24 | 25 | log = logging.getLogger(f"mkdocs.plugins.{__name__}") 26 | log.addFilter(warning_filter) 27 | 28 | 29 | class AutorefsPlugin(BasePlugin): 30 | """ 31 | An `mkdocs` plugin. 32 | 33 | This plugin defines the following event hooks: 34 | 35 | - `on_config` 36 | - `on_page_content` 37 | - `on_post_page` 38 | 39 | Check the [Developing Plugins](https://www.mkdocs.org/user-guide/plugins/#developing-plugins) page of `mkdocs` 40 | for more information about its plugin system. 41 | """ 42 | 43 | scan_toc: bool = True 44 | current_page: Optional[str] = None 45 | 46 | def __init__(self) -> None: 47 | """Initialize the object.""" 48 | super().__init__() 49 | self._url_map: Dict[str, str] = {} 50 | self.get_fallback_anchor: Callable[[str], Optional[str]] = lambda identifier: None 51 | 52 | def register_anchor(self, page: str, anchor: str): 53 | """ 54 | Register that an anchor corresponding to an identifier was encountered when rendering the page. 55 | 56 | Arguments: 57 | page: The relative URL of the current page. Examples: `'foo/bar/'`, `'foo/index.html'` 58 | anchor: The HTML anchor (without '#') as a string. 59 | """ 60 | self._url_map[anchor] = f"{page}#{anchor}" 61 | 62 | def get_item_url(self, anchor: str) -> str: 63 | """ 64 | Return a site-relative URL with anchor to the identifier, if it's present anywhere. 65 | 66 | Arguments: 67 | anchor: The identifier (one that [collect][mkdocstrings.handlers.base.BaseCollector.collect] can accept). 68 | 69 | Returns: 70 | A site-relative URL. 71 | 72 | Raises: 73 | KeyError: If there isn't an item by this identifier anywhere on the site. 74 | """ 75 | try: 76 | return self._url_map[anchor] 77 | except KeyError: 78 | new_anchor = self.get_fallback_anchor(anchor) 79 | if new_anchor and new_anchor in self._url_map: 80 | return self._url_map[new_anchor] 81 | raise 82 | 83 | def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613,R0201 (unused arguments, cannot be static) 84 | """ 85 | Instantiate our Markdown extension. 86 | 87 | Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config). 88 | In this hook, we instantiate our [`AutorefsExtension`][mkdocs_autorefs.references.AutorefsExtension] 89 | and add it to the list of Markdown extensions used by `mkdocs`. 90 | 91 | Arguments: 92 | config: The MkDocs config object. 93 | kwargs: Additional arguments passed by MkDocs. 94 | 95 | Returns: 96 | The modified config. 97 | """ 98 | log.debug("Adding AutorefsExtension to the list") 99 | config["markdown_extensions"].append(AutorefsExtension()) 100 | return config 101 | 102 | def on_page_markdown(self, markdown: str, page: Page, **kwargs) -> str: # noqa: W0613 (unused arguments) 103 | """ 104 | Remember which page is the current one. 105 | 106 | Arguments: 107 | markdown: Input Markdown. 108 | page: The related MkDocs page instance. 109 | kwargs: Additional arguments passed by MkDocs. 110 | 111 | Returns: 112 | The same Markdown. We only use this hook to map anchors to URLs. 113 | """ 114 | self.current_page = page.url 115 | return markdown 116 | 117 | def on_page_content(self, html: str, page: Page, **kwargs) -> str: # noqa: W0613 (unused arguments) 118 | """ 119 | Map anchors to URLs. 120 | 121 | Hook for the [`on_page_content` event](https://www.mkdocs.org/user-guide/plugins/#on_page_content). 122 | In this hook, we map the IDs of every anchor found in the table of contents to the anchors absolute URLs. 123 | This mapping will be used later to fix unresolved reference of the form `[title][identifier]` or 124 | `[identifier][]`. 125 | 126 | Arguments: 127 | html: HTML converted from Markdown. 128 | page: The related MkDocs page instance. 129 | kwargs: Additional arguments passed by MkDocs. 130 | 131 | Returns: 132 | The same HTML. We only use this hook to map anchors to URLs. 133 | """ 134 | if self.scan_toc: 135 | log.debug(f"Mapping identifiers to URLs for page {page.file.src_path}") 136 | for item in page.toc.items: 137 | self.map_urls(page.url, item) 138 | return html 139 | 140 | def map_urls(self, base_url: str, anchor: AnchorLink) -> None: 141 | """ 142 | Recurse on every anchor to map its ID to its absolute URL. 143 | 144 | This method populates `self.url_map` by side-effect. 145 | 146 | Arguments: 147 | base_url: The base URL to use as a prefix for each anchor's relative URL. 148 | anchor: The anchor to process and to recurse on. 149 | """ 150 | self.register_anchor(base_url, anchor.id) 151 | for child in anchor.children: 152 | self.map_urls(base_url, child) 153 | 154 | def on_post_page(self, output: str, page: Page, **kwargs) -> str: # noqa: W0613 (unused arguments) 155 | """ 156 | Fix cross-references. 157 | 158 | Hook for the [`on_post_page` event](https://www.mkdocs.org/user-guide/plugins/#on_post_page). 159 | In this hook, we try to fix unresolved references of the form `[title][identifier]` or `[identifier][]`. 160 | Doing that allows the user of `mkdocstrings` to cross-reference objects in their documentation strings. 161 | It uses the native Markdown syntax so it's easy to remember and use. 162 | 163 | We log a warning for each reference that we couldn't map to an URL, but try to be smart and ignore identifiers 164 | that do not look legitimate (sometimes documentation can contain strings matching 165 | our [`AUTO_REF_RE`][mkdocs_autorefs.references.AUTO_REF_RE] regular expression that did not intend to reference anything). 166 | We currently ignore references when their identifier contains a space or a slash. 167 | 168 | Arguments: 169 | output: HTML converted from Markdown. 170 | page: The related MkDocs page instance. 171 | kwargs: Additional arguments passed by MkDocs. 172 | 173 | Returns: 174 | Modified HTML. 175 | """ 176 | log.debug(f"Fixing references in page {page.file.src_path}") 177 | 178 | fixed_output, unmapped = fix_refs(output, page.url, self.get_item_url) 179 | 180 | if unmapped and log.isEnabledFor(logging.WARNING): 181 | for ref in unmapped: 182 | log.warning( 183 | f"{page.file.src_path}: Could not find cross-reference target '[{ref}]'", 184 | ) 185 | 186 | return fixed_output 187 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the "mkdocstrings" plugin for MkDocs. 3 | 4 | The plugin instantiates a Markdown extension ([`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension]), 5 | and adds it to the list of Markdown extensions used by `mkdocs` 6 | during the [`on_config` event hook](https://www.mkdocs.org/user-guide/plugins/#on_config). 7 | 8 | Once the documentation is built, the [`on_post_build` event hook](https://www.mkdocs.org/user-guide/plugins/#on_post_build) 9 | is triggered and calls the [`handlers.teardown()` method][mkdocstrings.handlers.base.Handlers.teardown]. This method is 10 | used to teardown the handlers that were instantiated during documentation buildup. 11 | 12 | Finally, when serving the documentation, it can add directories to watch 13 | during the [`on_serve` event hook](https://www.mkdocs.org/user-guide/plugins/#on_serve). 14 | """ 15 | 16 | import os 17 | from typing import Callable, Optional, Tuple 18 | 19 | from livereload import Server 20 | from mkdocs.config import Config 21 | from mkdocs.config.config_options import Type as MkType 22 | from mkdocs.plugins import BasePlugin 23 | from mkdocs.utils import write_file 24 | 25 | from mkdocs_autorefs.plugin import AutorefsPlugin 26 | from mkdocstrings.extension import MkdocstringsExtension 27 | from mkdocstrings.handlers.base import BaseHandler, Handlers 28 | from mkdocstrings.loggers import get_logger 29 | 30 | log = get_logger(__name__) 31 | 32 | SELECTION_OPTS_KEY: str = "selection" 33 | """The name of the selection parameter in YAML configuration blocks.""" 34 | RENDERING_OPTS_KEY: str = "rendering" 35 | """The name of the rendering parameter in YAML configuration blocks.""" 36 | 37 | 38 | class MkdocstringsPlugin(BasePlugin): 39 | """ 40 | An `mkdocs` plugin. 41 | 42 | This plugin defines the following event hooks: 43 | 44 | - `on_config` 45 | - `on_post_build` 46 | - `on_serve` 47 | 48 | Check the [Developing Plugins](https://www.mkdocs.org/user-guide/plugins/#developing-plugins) page of `mkdocs` 49 | for more information about its plugin system. 50 | """ 51 | 52 | config_scheme: Tuple[Tuple[str, MkType]] = ( 53 | ("watch", MkType(list, default=[])), # type: ignore 54 | ("handlers", MkType(dict, default={})), 55 | ("default_handler", MkType(str, default="python")), 56 | ("custom_templates", MkType(str, default=None)), 57 | ) 58 | """ 59 | The configuration options of `mkdocstrings`, written in `mkdocs.yml`. 60 | 61 | Available options are: 62 | 63 | - __`watch`__: A list of directories to watch. Only used when serving the documentation with mkdocs. 64 | Whenever a file changes in one of directories, the whole documentation is built again, and the browser refreshed. 65 | - __`default_handler`__: The default handler to use. The value is the name of the handler module. Default is "python". 66 | - __`handlers`__: Global configuration of handlers. You can set global configuration per handler, applied everywhere, 67 | but overridable in each "autodoc" instruction. Example: 68 | 69 | ```yaml 70 | plugins: 71 | - mkdocstrings: 72 | handlers: 73 | python: 74 | selection: 75 | selection_opt: true 76 | rendering: 77 | rendering_opt: "value" 78 | setup_commands: 79 | - "import os" 80 | - "import django" 81 | - "os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_djang_app.settings')" 82 | - "django.setup()" 83 | rust: 84 | selection: 85 | selection_opt: 2 86 | ``` 87 | """ 88 | 89 | css_filename = "assets/_mkdocstrings.css" 90 | 91 | def __init__(self) -> None: 92 | """Initialize the object.""" 93 | super().__init__() 94 | self._handlers: Optional[Handlers] = None 95 | 96 | @property 97 | def handlers(self) -> Handlers: 98 | """ 99 | Get the instance of [mkdocstrings.handlers.base.Handlers][] for this plugin/build. 100 | 101 | Raises: 102 | RuntimeError: If the plugin hasn't been initialized with a config. 103 | 104 | Returns: 105 | An instance of [mkdocstrings.handlers.base.Handlers][] (the same throughout the build). 106 | """ 107 | if not self._handlers: 108 | raise RuntimeError("The plugin hasn't been initialized with a config yet") 109 | return self._handlers 110 | 111 | def on_serve(self, server: Server, builder: Callable = None, **kwargs) -> Server: # noqa: W0613 (unused arguments) 112 | """ 113 | Watch directories. 114 | 115 | Hook for the [`on_serve` event](https://www.mkdocs.org/user-guide/plugins/#on_serve). 116 | In this hook, we add the directories specified in the plugin's configuration to the list of directories 117 | watched by `mkdocs`. Whenever a change occurs in one of these directories, the documentation is built again 118 | and the site reloaded. 119 | 120 | Arguments: 121 | server: The `livereload` server instance. 122 | builder: The function to build the site. 123 | kwargs: Additional arguments passed by MkDocs. 124 | 125 | Returns: 126 | The server instance. 127 | """ 128 | if builder is None: 129 | # The builder parameter was added in mkdocs v1.1.1. 130 | # See issue https://github.com/mkdocs/mkdocs/issues/1952. 131 | builder = list(server.watcher._tasks.values())[0]["func"] # noqa: W0212 (protected member) 132 | for element in self.config["watch"]: 133 | log.debug(f"Adding directory '{element}' to watcher") 134 | server.watch(element, builder) 135 | return server 136 | 137 | def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused arguments) 138 | """ 139 | Instantiate our Markdown extension. 140 | 141 | Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config). 142 | In this hook, we instantiate our [`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension] 143 | and add it to the list of Markdown extensions used by `mkdocs`. 144 | 145 | We pass this plugin's configuration dictionary to the extension when instantiating it (it will need it 146 | later when processing markdown to get handlers and their global configurations). 147 | 148 | Arguments: 149 | config: The MkDocs config object. 150 | kwargs: Additional arguments passed by MkDocs. 151 | 152 | Returns: 153 | The modified config. 154 | """ 155 | log.debug("Adding extension to the list") 156 | 157 | theme_name = None 158 | if config["theme"].name is None: 159 | theme_name = os.path.dirname(config["theme"].dirs[0]) 160 | else: 161 | theme_name = config["theme"].name 162 | 163 | extension_config = { 164 | "theme_name": theme_name, 165 | "mdx": config["markdown_extensions"], 166 | "mdx_configs": config["mdx_configs"], 167 | "mkdocstrings": self.config, 168 | } 169 | self._handlers = Handlers(extension_config) 170 | 171 | try: 172 | # If autorefs plugin is explicitly enabled, just use it. 173 | autorefs = config["plugins"]["autorefs"] 174 | log.debug(f"Picked up existing autorefs instance {autorefs!r}") 175 | except KeyError: 176 | # Otherwise, add a limited instance of it that acts only on what's added through `register_anchor`. 177 | autorefs = AutorefsPlugin() 178 | autorefs.scan_toc = False 179 | config["plugins"]["autorefs"] = autorefs 180 | log.debug(f"Added a subdued autorefs instance {autorefs!r}") 181 | # Add collector-based fallback in either case. 182 | autorefs.get_fallback_anchor = self._handlers.get_anchor 183 | 184 | mkdocstrings_extension = MkdocstringsExtension(extension_config, self._handlers, autorefs) 185 | config["markdown_extensions"].append(mkdocstrings_extension) 186 | 187 | config["extra_css"].insert(0, self.css_filename) # So that it has lower priority than user files. 188 | 189 | return config 190 | 191 | def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 (unused arguments, cannot be static) 192 | """ 193 | Teardown the handlers. 194 | 195 | Hook for the [`on_post_build` event](https://www.mkdocs.org/user-guide/plugins/#on_post_build). 196 | This hook is used to teardown all the handlers that were instantiated and cached during documentation buildup. 197 | 198 | For example, the [Python handler's collector][mkdocstrings.handlers.python.PythonCollector] opens a subprocess 199 | in the background and keeps it open to feed it the "autodoc" instructions and get back JSON data. Therefore, 200 | it must close it at some point, and it does it in its 201 | [`teardown()` method][mkdocstrings.handlers.python.PythonCollector.teardown] which is indirectly called by 202 | this hook. 203 | 204 | Arguments: 205 | config: The MkDocs config object. 206 | kwargs: Additional arguments passed by MkDocs. 207 | """ 208 | if self._handlers: 209 | css_content = "\n".join(handler.renderer.extra_css for handler in self.handlers.seen_handlers) 210 | write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], self.css_filename)) 211 | 212 | log.debug("Tearing handlers down") 213 | self._handlers.teardown() 214 | 215 | def get_handler(self, handler_name: str) -> BaseHandler: 216 | """ 217 | Get a handler by its name. See [mkdocstrings.handlers.base.Handlers.get_handler][]. 218 | 219 | Arguments: 220 | handler_name: The name of the handler. 221 | 222 | Returns: 223 | An instance of a subclass of [`BaseHandler`][mkdocstrings.handlers.base.BaseHandler]. 224 | """ 225 | return self.handlers.get_handler(handler_name) 226 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/extension.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module holds the code of the Markdown extension responsible for matching "autodoc" instructions. 3 | 4 | The extension is composed of a Markdown [block processor](https://python-markdown.github.io/extensions/api/#blockparser) 5 | that matches indented blocks starting with a line like '::: identifier'. 6 | 7 | For each of these blocks, it uses a [handler][mkdocstrings.handlers.base.BaseHandler] to collect documentation about 8 | the given identifier and render it with Jinja templates. 9 | 10 | Both the collection and rendering process can be configured by adding YAML configuration under the "autodoc" 11 | instruction: 12 | 13 | ```yaml 14 | ::: some.identifier 15 | handler: python 16 | selection: 17 | option1: value1 18 | option2: 19 | - value2a 20 | - value2b 21 | rendering: 22 | option_x: etc 23 | ``` 24 | """ 25 | import re 26 | from collections import ChainMap 27 | from typing import Mapping, MutableSequence, Sequence, Tuple 28 | from xml.etree.ElementTree import Element 29 | 30 | import yaml 31 | from jinja2.exceptions import TemplateNotFound 32 | from markdown import Markdown 33 | from markdown.blockparser import BlockParser 34 | from markdown.blockprocessors import BlockProcessor 35 | from markdown.extensions import Extension 36 | from markdown.treeprocessors import Treeprocessor 37 | 38 | from mkdocs_autorefs.plugin import AutorefsPlugin 39 | from mkdocstrings.handlers.base import CollectionError, CollectorItem, Handlers 40 | from mkdocstrings.loggers import get_logger 41 | 42 | log = get_logger(__name__) 43 | 44 | 45 | class AutoDocProcessor(BlockProcessor): 46 | """ 47 | Our "autodoc" Markdown block processor. 48 | 49 | It has a [`test` method][mkdocstrings.extension.AutoDocProcessor.test] that tells if a block matches a criterion, 50 | and a [`run` method][mkdocstrings.extension.AutoDocProcessor.run] that processes it. 51 | 52 | It also has utility methods allowing to get handlers and their configuration easily, useful when processing 53 | a matched block. 54 | """ 55 | 56 | regex = re.compile(r"^(?P<heading>#{1,6} *|)::: ?(?P<name>.+?) *$", flags=re.MULTILINE) 57 | 58 | def __init__( 59 | self, parser: BlockParser, md: Markdown, config: dict, handlers: Handlers, autorefs: AutorefsPlugin 60 | ) -> None: 61 | """ 62 | Initialize the object. 63 | 64 | Arguments: 65 | parser: A `markdown.blockparser.BlockParser` instance. 66 | md: A `markdown.Markdown` instance. 67 | config: The [configuration][mkdocstrings.plugin.MkdocstringsPlugin.config_scheme] 68 | of the `mkdocstrings` plugin. 69 | handlers: A [mkdocstrings.handlers.base.Handlers][] instance. 70 | autorefs: A [mkdocs_autorefs.plugin.AutorefsPlugin][] instance. 71 | """ 72 | super().__init__(parser=parser) 73 | self.md = md 74 | self._config = config 75 | self._handlers = handlers 76 | self._autorefs = autorefs 77 | self._updated_env = False 78 | 79 | def test(self, parent: Element, block: str) -> bool: 80 | """ 81 | Match our autodoc instructions. 82 | 83 | Arguments: 84 | parent: The parent element in the XML tree. 85 | block: The block to be tested. 86 | 87 | Returns: 88 | Whether this block should be processed or not. 89 | """ 90 | return bool(self.regex.search(block)) 91 | 92 | def run(self, parent: Element, blocks: MutableSequence[str]) -> None: 93 | """ 94 | Run code on the matched blocks. 95 | 96 | The identifier and configuration lines are retrieved from a matched block 97 | and used to collect and render an object. 98 | 99 | Arguments: 100 | parent: The parent element in the XML tree. 101 | blocks: The rest of the blocks to be processed. 102 | """ 103 | block = blocks.pop(0) 104 | match = self.regex.search(block) 105 | 106 | if match: 107 | if match.start() > 0: 108 | self.parser.parseBlocks(parent, [block[: match.start()]]) 109 | # removes the first line 110 | block = block[match.end() :] # type: ignore 111 | 112 | block, the_rest = self.detab(block) 113 | 114 | if match: 115 | identifier = match["name"] 116 | heading_level = match["heading"].count("#") 117 | log.debug(f"Matched '::: {identifier}'") 118 | 119 | html, headings = self._process_block(identifier, block, heading_level) 120 | el = Element("div", {"class": "mkdocstrings"}) 121 | # The final HTML is inserted as opaque to subsequent processing, and only revealed at the end. 122 | el.text = self.md.htmlStash.store(html) 123 | # So we need to duplicate the headings directly (and delete later), just so 'toc' can pick them up. 124 | el.extend(headings) 125 | 126 | for heading in headings: 127 | self._autorefs.register_anchor(self._autorefs.current_page, heading.attrib["id"]) 128 | 129 | parent.append(el) 130 | 131 | if the_rest: 132 | # This block contained unindented line(s) after the first indented 133 | # line. Insert these lines as the first block of the master blocks 134 | # list for future processing. 135 | blocks.insert(0, the_rest) 136 | 137 | def _process_block(self, identifier: str, yaml_block: str, heading_level: int = 0) -> Tuple[str, Sequence[Element]]: 138 | """ 139 | Process an autodoc block. 140 | 141 | Arguments: 142 | identifier: The identifier of the object to collect and render. 143 | yaml_block: The YAML configuration. 144 | heading_level: Suggested level of the the heading to insert (0 to ignore). 145 | 146 | Raises: 147 | CollectionError: When something wrong happened during collection. 148 | TemplateNotFound: When a template used for rendering could not be found. 149 | 150 | Returns: 151 | Rendered HTML and the list of heading elements encoutered. 152 | """ 153 | config = yaml.safe_load(yaml_block) or {} 154 | handler_name = self._handlers.get_handler_name(config) 155 | 156 | log.debug(f"Using handler '{handler_name}'") 157 | handler_config = self._handlers.get_handler_config(handler_name) 158 | handler = self._handlers.get_handler(handler_name, handler_config) 159 | 160 | selection, rendering = get_item_configs(handler_config, config) 161 | if heading_level: 162 | rendering = ChainMap(rendering, {"heading_level": heading_level}) # like setdefault 163 | 164 | log.debug("Collecting data") 165 | try: 166 | data: CollectorItem = handler.collector.collect(identifier, selection) 167 | except CollectionError: 168 | log.error(f"Could not collect '{identifier}'") 169 | raise 170 | 171 | if not self._updated_env: 172 | log.debug("Updating renderer's env") 173 | handler.renderer._update_env(self.md, self._config) # noqa: W0212 (protected member OK) 174 | self._updated_env = True 175 | 176 | log.debug("Rendering templates") 177 | try: 178 | rendered = handler.renderer.render(data, rendering) 179 | except TemplateNotFound as exc: 180 | theme_name = self._config["theme_name"] 181 | log.error( 182 | f"Template '{exc.name}' not found for '{handler_name}' handler and theme '{theme_name}'.", 183 | ) 184 | raise 185 | 186 | return (rendered, handler.renderer.get_headings()) 187 | 188 | 189 | def get_item_configs(handler_config: dict, config: dict) -> Tuple[Mapping, Mapping]: 190 | """ 191 | Get the selection and rendering configuration merged into the global configuration of the given handler. 192 | 193 | Arguments: 194 | handler_config: The global configuration of a handler. It can be an empty dictionary. 195 | config: The configuration to merge into the global handler configuration. 196 | 197 | Returns: 198 | Two dictionaries: selection and rendering. The local configurations are merged into the global ones. 199 | """ 200 | item_selection_config = ChainMap(config.get("selection", {}), handler_config.get("selection", {})) 201 | item_rendering_config = ChainMap(config.get("rendering", {}), handler_config.get("rendering", {})) 202 | return item_selection_config, item_rendering_config 203 | 204 | 205 | class _PostProcessor(Treeprocessor): 206 | def run(self, root: Element): 207 | carry_text = "" 208 | for el in reversed(root): # Reversed mainly for the ability to mutate during iteration. 209 | if el.tag == "div" and el.get("class") == "mkdocstrings": 210 | # Delete the duplicated headings along with their container, but keep the text (i.e. the actual HTML). 211 | carry_text = (el.text or "") + carry_text 212 | root.remove(el) 213 | elif carry_text: 214 | el.tail = (el.tail or "") + carry_text 215 | carry_text = "" 216 | if carry_text: 217 | root.text = (root.text or "") + carry_text 218 | 219 | 220 | class MkdocstringsExtension(Extension): 221 | """ 222 | Our Markdown extension. 223 | 224 | It cannot work outside of `mkdocstrings`. 225 | """ 226 | 227 | def __init__(self, config: dict, handlers: Handlers, autorefs: AutorefsPlugin, **kwargs) -> None: 228 | """ 229 | Initialize the object. 230 | 231 | Arguments: 232 | config: The configuration items from `mkdocs` and `mkdocstrings` that must be passed to the block processor 233 | when instantiated in [`extendMarkdown`][mkdocstrings.extension.MkdocstringsExtension.extendMarkdown]. 234 | handlers: A [mkdocstrings.handlers.base.Handlers][] instance. 235 | autorefs: A [mkdocs_autorefs.plugin.AutorefsPlugin][] instance. 236 | kwargs: Keyword arguments used by `markdown.extensions.Extension`. 237 | """ 238 | super().__init__(**kwargs) 239 | self._config = config 240 | self._handlers = handlers 241 | self._autorefs = autorefs 242 | 243 | def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent method's name) 244 | """ 245 | Register the extension. 246 | 247 | Add an instance of our [`AutoDocProcessor`][mkdocstrings.extension.AutoDocProcessor] to the Markdown parser. 248 | 249 | Arguments: 250 | md: A `markdown.Markdown` instance. 251 | """ 252 | md.parser.blockprocessors.register( 253 | AutoDocProcessor(md.parser, md, self._config, self._handlers, self._autorefs), 254 | "mkdocstrings", 255 | priority=75, # Right before markdown.blockprocessors.HashHeaderProcessor 256 | ) 257 | md.treeprocessors.register( 258 | _PostProcessor(md.parser), 259 | "mkdocstrings_post", 260 | priority=4, # Right after 'toc'. 261 | ) 262 | -------------------------------------------------------------------------------- /docs/src/mkdocstrings/handlers/python.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements a handler for the Python language. 3 | 4 | The handler collects data with [`pytkdocs`](https://github.com/pawamoy/pytkdocs). 5 | """ 6 | 7 | import json 8 | import os 9 | import sys 10 | from collections import ChainMap 11 | from subprocess import PIPE, Popen # noqa: S404 (what other option, more secure that PIPE do we have? sockets?) 12 | from typing import Any, List, Optional 13 | 14 | from markdown import Markdown 15 | 16 | from mkdocstrings.handlers.base import BaseCollector, BaseHandler, BaseRenderer, CollectionError, CollectorItem 17 | from mkdocstrings.loggers import get_logger 18 | 19 | log = get_logger(__name__) 20 | 21 | 22 | class PythonRenderer(BaseRenderer): 23 | """ 24 | The class responsible for loading Jinja templates and rendering them. 25 | 26 | It defines some configuration options, implements the `render` method, 27 | and overrides the `update_env` method of the [`BaseRenderer` class][mkdocstrings.handlers.base.BaseRenderer]. 28 | 29 | Attributes: 30 | fallback_theme: The theme to fallback to. 31 | default_config: The default rendering options, 32 | see [`default_config`][mkdocstrings.handlers.python.PythonRenderer.default_config]. 33 | """ 34 | 35 | fallback_theme = "material" 36 | 37 | default_config: dict = { 38 | "show_root_heading": False, 39 | "show_root_toc_entry": True, 40 | "show_root_full_path": True, 41 | "show_root_members_full_path": False, 42 | "show_object_full_path": False, 43 | "show_category_heading": False, 44 | "show_if_no_docstring": False, 45 | "show_signature_annotations": False, 46 | "show_source": True, 47 | "group_by_category": True, 48 | "heading_level": 2, 49 | } 50 | """ 51 | The default rendering options. 52 | 53 | Option | Type | Description | Default 54 | ------ | ---- | ----------- | ------- 55 | **`show_root_heading`** | `bool` | Show the heading of the object at the root of the documentation tree. | `False` 56 | **`show_root_toc_entry`** | `bool` | If the root heading is not shown, at least add a ToC entry for it. | `True` 57 | **`show_root_full_path`** | `bool` | Show the full Python path for the root object heading. | `True` 58 | **`show_object_full_path`** | `bool` | Show the full Python path of every object. | `False` 59 | **`show_root_members_full_path`** | `bool` | Show the full Python path of objects that are children of the root object (for example, classes in a module). When False, `show_object_full_path` overrides. | `False` 60 | **`show_category_heading`** | `bool` | When grouped by categories, show a heading for each category. | `False` 61 | **`show_if_no_docstring`** | `bool` | Show the object heading even if it has no docstring or children with docstrings. | `False` 62 | **`show_signature_annotations`** | `bool` | Show the type annotations in methods and functions signatures. | `False` 63 | **`show_source`** | `bool` | Show the source code of this object. | `True` 64 | **`group_by_category`** | `bool` | Group the object's children by categories: attributes, classes, functions, methods, and modules. | `True` 65 | **`heading_level`** | `int` | The initial heading level to use. | `2` 66 | """ # noqa: E501 67 | 68 | def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignore missing docstring) 69 | final_config = ChainMap(config, self.default_config) 70 | 71 | template = self.env.get_template(f"{data['category']}.html") 72 | 73 | # Heading level is a "state" variable, that will change at each step 74 | # of the rendering recursion. Therefore, it's easier to use it as a plain value 75 | # than as an item in a dictionary. 76 | heading_level = final_config["heading_level"] 77 | 78 | return template.render( 79 | **{"config": final_config, data["category"]: data, "heading_level": heading_level, "root": True}, 80 | ) 81 | 82 | def get_anchor(self, data: CollectorItem) -> str: # noqa: D102 (ignore missing docstring) 83 | return data.get("path") 84 | 85 | def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore missing docstring) 86 | super().update_env(md, config) 87 | self.env.trim_blocks = True 88 | self.env.lstrip_blocks = True 89 | self.env.keep_trailing_newline = False 90 | 91 | 92 | class PythonCollector(BaseCollector): 93 | """ 94 | The class responsible for loading Jinja templates and rendering them. 95 | 96 | It defines some configuration options, implements the `render` method, 97 | and overrides the `update_env` method of the [`BaseRenderer` class][mkdocstrings.handlers.base.BaseRenderer]. 98 | """ 99 | 100 | default_config: dict = {"filters": ["!^_[^_]"]} 101 | """ 102 | The default selection options. 103 | 104 | Option | Type | Description | Default 105 | ------ | ---- | ----------- | ------- 106 | **`filters`** | `List[str]` | Filter members with regular expressions. | `[ "!^_[^_]" ]` 107 | **`members`** | `Union[bool, List[str]]` | Explicitly select the object members. | *`pytkdocs` default: `True`* 108 | 109 | If `members` is a list of names, filters are applied only on the members children (not the members themselves). 110 | If `members` is `False`, none are selected. 111 | If `members` is `True` or an empty list, filters are applied on all members and their children. 112 | 113 | Members affect only the first layer of objects, while filters affect the whole object-tree recursively. 114 | 115 | Every filters is run against every object name. An object can be un-selected by a filter and re-selected by the 116 | next one: 117 | 118 | - `"!^_"`: exclude all objects starting with an underscore 119 | - `"^__"`: but select all objects starting with **two** underscores 120 | 121 | Obviously one could use a single filter instead: `"!^_[^_]"`, which is the default. 122 | """ 123 | 124 | def __init__(self, setup_commands: Optional[List[str]] = None) -> None: 125 | """ 126 | Initialize the object. 127 | 128 | When instantiating a Python collector, we open a subprocess in the background with `subprocess.Popen`. 129 | It will allow us to feed input to and read output from this subprocess, keeping it alive during 130 | the whole documentation generation. Spawning a new Python subprocess for each "autodoc" instruction would be 131 | too resource intensive, and would slow down `mkdocstrings` a lot. 132 | 133 | Arguments: 134 | setup_commands: A list of python commands as strings to be executed in the subprocess before `pytkdocs`. 135 | """ 136 | log.debug("Opening 'pytkdocs' subprocess") 137 | env = os.environ.copy() 138 | env["PYTHONUNBUFFERED"] = "1" 139 | 140 | if setup_commands: 141 | # prevent the Python interpreter or the setup commands 142 | # from writing to stdout as it would break pytkdocs output 143 | commands = [ 144 | "import sys", 145 | "from io import StringIO", 146 | "from pytkdocs.cli import main as pytkdocs", 147 | "sys.stdout = StringIO()", # redirect stdout to memory buffer 148 | *setup_commands, 149 | "sys.stdout.flush()", 150 | "sys.stdout = sys.__stdout__", # restore stdout 151 | "pytkdocs(['--line-by-line'])", 152 | ] 153 | cmd = [sys.executable, "-c", "; ".join(commands)] 154 | else: 155 | cmd = [sys.executable, "-m", "pytkdocs", "--line-by-line"] 156 | 157 | self.process = Popen( # noqa: S603,S607 (we trust the input, and we don't want to use the absolute path) 158 | cmd, 159 | universal_newlines=True, 160 | stderr=PIPE, 161 | stdout=PIPE, 162 | stdin=PIPE, 163 | bufsize=-1, 164 | env=env, 165 | ) 166 | 167 | def collect(self, identifier: str, config: dict) -> CollectorItem: 168 | """ 169 | Collect the documentation tree given an identifier and selection options. 170 | 171 | In this method, we feed one line of JSON to the standard input of the subprocess that was opened 172 | during instantiation of the collector. Then we read one line of JSON on its standard output. 173 | 174 | We load back the JSON text into a Python dictionary. 175 | If there is a decoding error, we log it as error and raise a CollectionError. 176 | 177 | If the dictionary contains an `error` key, we log it as error (with the optional `traceback` value), 178 | and raise a CollectionError. 179 | 180 | If the dictionary values for keys `loading_errors` and `parsing_errors` are not empty, 181 | we log them as warnings. 182 | 183 | Then we pick up the only object within the `objects` list (there's always only one, because we collect 184 | them one by one), rebuild it's categories lists 185 | (see [`rebuild_category_lists()`][mkdocstrings.handlers.python.rebuild_category_lists]), 186 | and return it. 187 | 188 | Arguments: 189 | identifier: The dotted-path of a Python object available in the Python path. 190 | config: Selection options, used to alter the data collection done by `pytkdocs`. 191 | 192 | Raises: 193 | CollectionError: When there was a problem collecting the object documentation. 194 | 195 | Returns: 196 | The collected object-tree. 197 | """ 198 | final_config = ChainMap(config, self.default_config) 199 | 200 | log.debug("Preparing input") 201 | json_input = json.dumps({"objects": [{"path": identifier, **final_config}]}) 202 | 203 | log.debug("Writing to process' stdin") 204 | self.process.stdin.write(json_input + "\n") # type: ignore 205 | self.process.stdin.flush() # type: ignore 206 | 207 | log.debug("Reading process' stdout") 208 | stdout = self.process.stdout.readline() # type: ignore 209 | 210 | log.debug("Loading JSON output as Python object") 211 | try: 212 | result = json.loads(stdout) 213 | except json.decoder.JSONDecodeError as exception: 214 | log.error(f"Error while loading JSON: {stdout}") 215 | raise CollectionError(str(exception)) from exception 216 | 217 | error = result.get("error") 218 | if error: 219 | message = f"Collection failed: {error}" 220 | if "traceback" in result: 221 | message += f"\n{result['traceback']}" 222 | log.error(message) 223 | raise CollectionError(error) 224 | 225 | for loading_error in result["loading_errors"]: 226 | log.warning(loading_error) 227 | 228 | for errors in result["parsing_errors"].values(): 229 | for parsing_error in errors: 230 | log.warning(parsing_error) 231 | 232 | # We always collect only one object at a time 233 | result = result["objects"][0] 234 | 235 | log.debug("Rebuilding categories and children lists") 236 | rebuild_category_lists(result) 237 | 238 | return result 239 | 240 | def teardown(self) -> None: 241 | """Terminate the opened subprocess, set it to `None`.""" 242 | log.debug("Tearing process down") 243 | self.process.terminate() 244 | 245 | 246 | class PythonHandler(BaseHandler): 247 | """The Python handler class, nothing specific here.""" 248 | 249 | 250 | def get_handler( 251 | theme: str, # noqa: W0613 (unused argument config) 252 | custom_templates: Optional[str] = None, 253 | setup_commands: Optional[List[str]] = None, 254 | **config: Any, 255 | ) -> PythonHandler: 256 | """ 257 | Simply return an instance of `PythonHandler`. 258 | 259 | Arguments: 260 | theme: The theme to use when rendering contents. 261 | custom_templates: Directory containing custom templates. 262 | setup_commands: A list of commands as strings to be executed in the subprocess before `pytkdocs`. 263 | config: Configuration passed to the handler. 264 | 265 | Returns: 266 | An instance of `PythonHandler`. 267 | """ 268 | return PythonHandler( 269 | collector=PythonCollector(setup_commands=setup_commands), 270 | renderer=PythonRenderer("python", theme, custom_templates), 271 | ) 272 | 273 | 274 | def rebuild_category_lists(obj: dict) -> None: 275 | """ 276 | Recursively rebuild the category lists of a collected object. 277 | 278 | Since `pytkdocs` dumps JSON on standard output, it must serialize the object-tree and flatten it to reduce data 279 | duplication and avoid cycle-references. Indeed, each node of the object-tree has a `children` list, containing 280 | all children, and another list for each category of children: `attributes`, `classes`, `functions`, `methods` 281 | and `modules`. It replaces the values in category lists with only the paths of the objects. 282 | 283 | Here, we reconstruct these category lists by picking objects in the `children` list using their path. 284 | 285 | For each object, we recurse on every one of its children. 286 | 287 | Arguments: 288 | obj: The collected object, loaded back from JSON into a Python dictionary. 289 | """ 290 | for category in ("attributes", "classes", "functions", "methods", "modules"): 291 | obj[category] = [obj["children"][path] for path in obj[category]] 292 | obj["children"] = [child for _, child in obj["children"].items()] 293 | for child in obj["children"]: 294 | rebuild_category_lists(child) 295 | -------------------------------------------------------------------------------- /pure_ocean_breeze/jason/data/read_data.py: -------------------------------------------------------------------------------- 1 | __updated__ = "2025-06-25 21:06:31" 2 | 3 | import os 4 | import numpy as np 5 | import pandas as pd 6 | from typing import Union 7 | import rust_pyfunc as rp 8 | 9 | from pure_ocean_breeze.jason.state.states import STATES 10 | from pure_ocean_breeze.jason.state.homeplace import HomePlace 11 | from pure_ocean_breeze.jason.state.decorators import * 12 | from pure_ocean_breeze.jason.data.tools import boom_one 13 | from cachier import cachier 14 | 15 | try: 16 | homeplace = HomePlace() 17 | except Exception: 18 | print("您暂未初始化,功能将受限") 19 | 20 | 21 | @cachier() 22 | def read_daily( 23 | open: bool = 0, 24 | close: bool = 0, 25 | high: bool = 0, 26 | low: bool = 0, 27 | vwap: bool = 0, 28 | tr: bool = 0, 29 | sharenum: bool = 0, 30 | total_sharenum: bool = 0, 31 | amount: bool = 0, 32 | money: bool = 0, 33 | flow_cap: bool = 0, 34 | total_cap: bool = 0, 35 | adjfactor: bool = 0, 36 | state: bool = 0, 37 | state_loose: bool = 0, 38 | unadjust: bool = 0, 39 | ret: bool = 0, 40 | ret_inday: bool = 0, 41 | ret_night: bool = 0, 42 | vol: bool = 0, 43 | vol_inday: bool = 0, 44 | vol_night: bool = 0, 45 | swing: bool = 0, 46 | stop_up: bool = 0, 47 | stop_down: bool = 0, 48 | up_down_limit_status:bool=0, 49 | swindustry_dummy: bool = 0, 50 | start: Union[int, str] = STATES["START"], 51 | ) -> pd.DataFrame: 52 | """直接读取常用的量价读取日频数据,默认为复权价格, 53 | 在 open,close,high,low,tr,sharenum,volume 中选择一个参数指定为1 54 | 55 | Parameters 56 | ---------- 57 | open : bool, optional 58 | 为1则选择读取开盘价, by default 0 59 | close : bool, optional 60 | 为1则选择读取收盘价, by default 0 61 | high : bool, optional 62 | 为1则选择读取最高价, by default 0 63 | low : bool, optional 64 | 为1则选择读取最低价, by default 0 65 | vwap : bool, optional 66 | 为1则选择读取日均成交价, by default 0 67 | tr : bool, optional 68 | 为1则选择读取换手率, by default 0 69 | sharenum : bool, optional 70 | 为1则选择读取流通股数, by default 0 71 | total_sharenum : bool, optional 72 | 为1则表示读取总股数, by default 0 73 | amount : bool, optional 74 | 为1则选择读取成交量, by default 0 75 | money : bool, optional 76 | 为1则表示读取成交额, by default 0 77 | flow_cap : bool, optional 78 | 为1则选择读取流通市值, by default 0 79 | total_cap : bool, optional 80 | 为1则选择读取总市值, by default 0 81 | adjfactor : bool, optional 82 | 为1则选择读取复权因子, by default 0 83 | state : bool, optional 84 | 为1则选择读取当日交易状态是否正常,1表示正常交易,空值则不是, by default 0 85 | state_loose : bool, optional 86 | 为1则选择读取当日交易状态是否正常,1表示正常交易,空值则不是, by default 0 87 | unadjust : bool, optional 88 | 为1则将上述价格改为不复权价格, by default 0 89 | ret : bool, optional 90 | 为1则选择读取日间收益率, by default 0 91 | ret_inday : bool, optional 92 | 为1则表示读取日内收益率, by default 0 93 | ret_night : bool, optional 94 | 为1则表示读取隔夜波动率, by default 0 95 | vol : bool, optional 96 | 为1则选择读取滚动20日日间波动率, by default 0 97 | vol_inday : bool, optional 98 | 为1则表示读取滚动20日日内收益率波动率, by default 0 99 | vol_night : bool, optional 100 | 为1则表示读取滚动20日隔夜收益率波动率, by default 0 101 | swing : bool, optional 102 | 为1则表示读取振幅, by default 0 103 | stop_up : bool, optional 104 | 为1则表示读取每只股票涨停价, by default 0 105 | stop_down : bool, optional 106 | 为1则表示读取每只股票跌停价, by default 0 107 | swindustry_dummy : bool, optional 108 | 为1则表示读取申万一级行业哑变量, by default 0 109 | start : Union[int,str], optional 110 | 起始日期,形如20130101, by default STATES["START"] 111 | 112 | Returns 113 | ------- 114 | `pd.DataFrame` 115 | 一个columns为股票代码,index为时间,values为目标数据的pd.DataFrame 116 | 117 | Raises 118 | ------ 119 | `IOError` 120 | open,close,high,low,tr,sharenum,volume 都为0时,将报错 121 | """ 122 | 123 | if not unadjust: 124 | if open: 125 | df = pd.read_parquet( 126 | homeplace.daily_data_file + "opens.parquet" 127 | ) * read_daily(state=1, start=start) 128 | elif close: 129 | df = pd.read_parquet( 130 | homeplace.daily_data_file + "closes.parquet" 131 | ) * read_daily(state=1, start=start) 132 | elif high: 133 | df = pd.read_parquet( 134 | homeplace.daily_data_file + "highs.parquet" 135 | ) * read_daily(state=1, start=start) 136 | elif low: 137 | df = pd.read_parquet( 138 | homeplace.daily_data_file + "lows.parquet" 139 | ) * read_daily(state=1, start=start) 140 | elif vwap: 141 | df = ( 142 | pd.read_parquet(homeplace.daily_data_file + "vwaps.parquet") 143 | * read_daily(adjfactor=1, start=start) 144 | *read_daily(state=1, start=start) 145 | ) 146 | elif tr: 147 | df = pd.read_parquet(homeplace.daily_data_file + "trs.parquet").replace( 148 | 0, np.nan 149 | ) *read_daily(state=1, start=start) 150 | elif sharenum: 151 | df = pd.read_parquet(homeplace.daily_data_file + "sharenums.parquet") 152 | elif total_sharenum: 153 | df = pd.read_parquet(homeplace.daily_data_file + "total_sharenums.parquet") 154 | elif amount: 155 | df = pd.read_parquet( 156 | homeplace.daily_data_file + "amounts.parquet" 157 | ) * read_daily(state=1, start=start) 158 | elif money: 159 | df = pd.read_parquet( 160 | homeplace.daily_data_file + "moneys.parquet" 161 | )*read_daily(state=1, start=start) 162 | elif flow_cap: 163 | df = pd.read_parquet(homeplace.daily_data_file + "flow_caps.parquet") 164 | elif total_cap: 165 | df = pd.read_parquet(homeplace.daily_data_file + "total_caps.parquet") 166 | elif adjfactor: 167 | df=pd.read_parquet(homeplace.daily_data_file+'adjfactors.parquet') 168 | elif state: 169 | df = pd.read_parquet(homeplace.daily_data_file + "states.parquet") 170 | elif state_loose: 171 | df = pd.read_parquet(homeplace.daily_data_file + "states_loose.parquet") 172 | elif ret: 173 | df = read_daily(close=1, start=start) 174 | df = df / df.shift(1) - 1 175 | elif ret_inday: 176 | df = read_daily(close=1, start=start) / read_daily(open=1, start=start) - 1 177 | elif ret_night: 178 | df = ( 179 | read_daily(open=1, start=start) 180 | / read_daily(close=1, start=start).shift(1) 181 | - 1 182 | ) 183 | elif vol: 184 | df = read_daily(ret=1, start=start) 185 | df = df.rolling(20, min_periods=10).std() 186 | elif vol_inday: 187 | df = read_daily(ret_inday=1, start=start) 188 | df = df.rolling(20, min_periods=10).std() 189 | elif vol_night: 190 | df = read_daily(ret_night=1, start=start) 191 | df = df.rolling(20, min_periods=10).std() 192 | elif swing: 193 | df = ( 194 | read_daily(high=1, start=start) - read_daily(low=1, start=start) 195 | ) / read_daily(close=1, start=start).shift(1) 196 | elif stop_up: 197 | df = ( 198 | pd.read_parquet(homeplace.daily_data_file + "stop_ups.parquet") 199 | * read_daily(adjfactor=1, start=start) 200 | * read_daily(state=1, start=start) 201 | ) 202 | elif stop_down: 203 | df = ( 204 | pd.read_parquet(homeplace.daily_data_file + "stop_downs.parquet") 205 | * read_daily(adjfactor=1, start=start) 206 | * read_daily(state=1, start=start) 207 | ) 208 | elif up_down_limit_status: 209 | df=pd.read_parquet(homeplace.daily_data_file+'up_down_limit_status.parquet') 210 | elif swindustry_dummy: 211 | df=pd.read_parquet(homeplace.daily_data_file+'sw_industry_level1_dummies.parquet') 212 | else: 213 | raise IOError("阁下总得读点什么吧?🤒") 214 | else: 215 | if open: 216 | df = pd.read_parquet( 217 | homeplace.daily_data_file + "opens_unadj.parquet" 218 | ) * read_daily(state=1, start=start) 219 | elif close: 220 | df = pd.read_parquet( 221 | homeplace.daily_data_file + "closes_unadj.parquet" 222 | ) * read_daily(state=1, start=start) 223 | elif high: 224 | df = pd.read_parquet( 225 | homeplace.daily_data_file + "highs_unadj.parquet" 226 | ) * read_daily(state=1, start=start) 227 | elif low: 228 | df = pd.read_parquet( 229 | homeplace.daily_data_file + "lows_unadj.parquet" 230 | ) * read_daily(state=1, start=start) 231 | elif vwap: 232 | df = pd.read_parquet( 233 | homeplace.daily_data_file + "vwaps.parquet" 234 | ) * read_daily(state=1, start=start) 235 | elif stop_up: 236 | df = pd.read_parquet( 237 | homeplace.daily_data_file + "stop_ups.parquet" 238 | ) * read_daily(state=1, start=start) 239 | elif stop_down: 240 | df = pd.read_parquet( 241 | homeplace.daily_data_file + "stop_downs.parquet" 242 | ) * read_daily(state=1, start=start) 243 | else: 244 | raise IOError("阁下总得读点什么吧?🤒") 245 | if "date" not in df.columns: 246 | df = df[df.index >= pd.Timestamp(str(start))] 247 | return df.dropna(how="all") 248 | 249 | 250 | def get_industry_dummies( 251 | daily: bool = 0, 252 | weekly: bool = 0, 253 | start: int = STATES["START"], 254 | ) -> dict: 255 | """生成30/31个行业的哑变量矩阵,返回一个字典 256 | 257 | Parameters 258 | ---------- 259 | daily : bool, optional 260 | 返回日频的哑变量, by default 0 261 | weekly : bool, optional 262 | 返回week频的哑变量, by default 0 263 | start : int, optional 264 | 起始日期, by default STATES["START"] 265 | 266 | Returns 267 | ------- 268 | `Dict` 269 | 各个行业及其哑变量构成的字典 270 | 271 | Raises 272 | ------ 273 | `ValueError` 274 | 如果未指定频率,将报错 275 | """ 276 | homeplace = HomePlace() 277 | name='sw_industry_level1_dummies.parquet' 278 | if weekly: 279 | industry_dummy = pd.read_parquet(homeplace.daily_data_file + name) 280 | industry_dummy = ( 281 | industry_dummy.set_index("date") 282 | .groupby("code") 283 | .resample("W") 284 | .last() 285 | .fillna(0) 286 | .drop(columns=["code"]) 287 | .reset_index() 288 | ) 289 | elif daily: 290 | industry_dummy = pd.read_parquet(homeplace.daily_data_file + name).fillna(0) 291 | else: 292 | raise ValueError("您总得指定一个频率吧?🤒") 293 | industry_dummy = industry_dummy[industry_dummy.date >= pd.Timestamp(str(start))] 294 | ws = list(industry_dummy.columns)[2:] 295 | ress = {} 296 | for w in ws: 297 | df = industry_dummy[["date", "code", w]] 298 | df = df.pivot(index="date", columns="code", values=w) 299 | df = df.replace(0, np.nan) 300 | ress[w] = df 301 | return ress 302 | 303 | 304 | @cachier() 305 | def read_index( 306 | open: bool = 0, 307 | close: bool = 0, 308 | high: bool = 0, 309 | low: bool = 0, 310 | start: int = STATES["START"], 311 | every_stock: bool = 1, 312 | sh50: bool = 0, 313 | hs300: bool = 0, 314 | zz500: bool = 0, 315 | zz1000: bool = 0, 316 | ) -> Union[pd.DataFrame, pd.Series]: 317 | """读取中证全指日行情数据 318 | 319 | Parameters 320 | ---------- 321 | open : bool, optional 322 | 读取开盘点数, by default 0 323 | close : bool, optional 324 | 读取收盘点数, by default 0 325 | high : bool, optional 326 | 读取最高点数, by default 0 327 | low : bool, optional 328 | 读取最低点数, by default 0 329 | start : int, optional 330 | 读取的起始日期, by default STATES["START"] 331 | every_stock : bool, optional 332 | 是否修改为index是时间,columns是每只股票代码,每一列值都相同的形式, by default 1 333 | sh50 : bool, optional 334 | 是否读取上证50, by default 0 335 | hs300 : bool, optional 336 | 是否读取沪深300, by default 0 337 | zz500 : bool, optional 338 | 是否读取中证500, by default 0 339 | zz1000 : bool, optional 340 | 是否读取中证1000, by default 0 341 | 342 | Returns 343 | ------- 344 | Union[pd.DataFrame,pd.Series] 345 | 读取market_index的行情数据 346 | 347 | Raises 348 | ------ 349 | IOError 350 | 如果没有指定任何指数,将报错 351 | """ 352 | homeplace=HomePlace() 353 | if open: 354 | # 米筐的第一分钟是集合竞价,第一分钟的收盘价即为当天开盘价 355 | df = pd.read_parquet(homeplace.daily_data_file + "index_opens.parquet") 356 | elif close: 357 | df = pd.read_parquet(homeplace.daily_data_file + "index_closes.parquet") 358 | elif high: 359 | df = pd.read_parquet(homeplace.daily_data_file + "index_highs.parquet") 360 | elif low: 361 | df = pd.read_parquet(homeplace.daily_data_file + "index_lows.parquet") 362 | else: 363 | raise IOError("总得指定一个指标吧?🤒") 364 | if sh50: 365 | df = df["000016.SH"] 366 | elif hs300: 367 | df = df["000300.SH"] 368 | elif zz500: 369 | df = df["000905.SH"] 370 | elif zz1000: 371 | df = df["000852.SH"] 372 | else: 373 | raise IOError("总得指定一个指数吧?🤒") 374 | if every_stock: 375 | tr = read_daily(tr=1, start=start) 376 | df = pd.DataFrame({k: list(df) for k in list(tr.columns)}, index=df.index) 377 | return df 378 | 379 | 380 | @cachier() 381 | def moon_read_dummy(freq): 382 | def deal_dummy(industry_dummy): 383 | industry_dummy = industry_dummy.drop(columns=["code"]).reset_index() 384 | industry_ws = [f"w{i}" for i in range(1, industry_dummy.shape[1] - 1)] 385 | col = ["code", "date"] + industry_ws 386 | industry_dummy.columns = col 387 | industry_dummy = industry_dummy[ 388 | industry_dummy.date >= pd.Timestamp(str(STATES["START"])) 389 | ] 390 | return industry_dummy 391 | 392 | # week_here 393 | swindustry_dummy = ( 394 | pd.read_parquet( 395 | homeplace.daily_data_file + "sw_industry_level1_dummies.parquet" 396 | ) 397 | .fillna(0) 398 | .set_index("date") 399 | .groupby("code") 400 | .resample(freq) 401 | .last() 402 | ) 403 | return deal_dummy(swindustry_dummy) 404 | 405 | 406 | 407 | 408 | def read_trade(symbol:str, date:int,with_retreat:int=0)->pd.DataFrame: 409 | file_name = "%s_%d_%s.csv" % (symbol, date, "transaction") 410 | file_path = os.path.join("/ssd_data/stock", str(date), "transaction", file_name) 411 | df= pd.read_csv( 412 | file_path, 413 | dtype={"symbol": str}, 414 | usecols=[ 415 | "exchtime", 416 | "price", 417 | "volume", 418 | "turnover", 419 | "flag", 420 | "index", 421 | "localtime", 422 | "ask_order", 423 | "bid_order", 424 | ], 425 | memory_map=True, 426 | engine="c", 427 | low_memory=False, 428 | ) 429 | if not with_retreat: 430 | df=df[df.flag!=32] 431 | df.exchtime=pd.to_timedelta(df.exchtime/1e6,unit='s')+pd.Timestamp('1970-01-01 08:00:00') 432 | return df 433 | 434 | def read_market(symbol:str, date:int)->pd.DataFrame: 435 | file_name = "%s_%d_%s.csv" % (symbol, date, "market_data") 436 | file_path = os.path.join("/ssd_data/stock", str(date), "market_data", file_name) 437 | df= pd.read_csv( 438 | file_path, 439 | dtype={"symbol": str}, 440 | memory_map=True, 441 | engine="c", 442 | low_memory=False, 443 | ) 444 | df.exchtime=pd.to_timedelta(df.exchtime/1e6,unit='s')+pd.Timestamp('1970-01-01 08:00:00') 445 | return df 446 | 447 | def read_market_pair(symbol:str, date:int)->tuple[pd.DataFrame,pd.DataFrame]: 448 | df=read_market(symbol,date) 449 | df = df[df.last_prc != 0] 450 | ask_prc_cols = [f"ask_prc{i}" for i in range(1, 11)] 451 | ask_vol_cols = [f"ask_vol{i}" for i in range(1, 11)] 452 | asks = pd.concat( 453 | [ 454 | pd.melt( 455 | df[ask_prc_cols + ["exchtime"]], 456 | id_vars=["exchtime"], 457 | value_name="price", 458 | ) 459 | .rename(columns={"variable": "number"}) 460 | .set_index("exchtime"), 461 | pd.melt( 462 | df[ask_vol_cols + ["exchtime"]], 463 | id_vars=["exchtime"], 464 | value_name="vol", 465 | ) 466 | .drop(columns=["variable"]) 467 | .set_index("exchtime"), 468 | ], 469 | axis=1, 470 | ) 471 | asks=asks[asks.price!=0] 472 | asks.number=asks.number.str.slice(7).astype(int) 473 | asks=asks.reset_index().sort_values(by=["exchtime", "number"]).reset_index(drop=True) 474 | 475 | bid_prc_cols = [f"bid_prc{i}" for i in range(1, 11)] 476 | bid_vol_cols = [f"bid_vol{i}" for i in range(1, 11)] 477 | bids = pd.concat( 478 | [ 479 | pd.melt( 480 | df[bid_prc_cols + ["exchtime"]], 481 | id_vars=["exchtime"], 482 | value_name="price", 483 | ) 484 | .rename(columns={"variable": "number"}) 485 | .set_index("exchtime"), 486 | pd.melt( 487 | df[bid_vol_cols + ["exchtime"]], 488 | id_vars=["exchtime"], 489 | value_name="vol", 490 | ) 491 | .drop(columns=["variable"]) 492 | .set_index("exchtime"), 493 | ], 494 | axis=1, 495 | ) 496 | bids=bids[bids.price!=0] 497 | bids.number=bids.number.str.slice(7).astype(int) 498 | bids=bids.reset_index().sort_values(by=["exchtime", "number"]).reset_index(drop=True) 499 | return asks, bids 500 | 501 | 502 | def adjust_afternoon(df: pd.DataFrame,only_inday:int=1) -> pd.DataFrame: 503 | start='09:30:00' if only_inday else '09:00:00' 504 | end='14:57:00' if only_inday else '15:00:00' 505 | if df.index.name=='exchtime': 506 | df1=df.between_time(start,'11:30:00') 507 | df2=df.between_time('13:00:00',end) 508 | df2.index=df2.index-pd.Timedelta(minutes=90) 509 | df=pd.concat([df1,df2]) 510 | elif 'exchtime' in df.columns: 511 | df1=df.set_index('exchtime').between_time(start,'11:30:00') 512 | df2=df.set_index('exchtime').between_time('13:00:00',end) 513 | df2.index=df2.index-pd.Timedelta(minutes=90) 514 | df=pd.concat([df1,df2]).reset_index() 515 | return df 516 | 517 | 518 | def query_backup_df(path): 519 | df=rp.query_backup_fast(path) 520 | df=pd.DataFrame(df['factors']).assign(code=df['code'],date=df['date']) 521 | df=df[['code','date']+list(df.columns[:-2])] 522 | return df -------------------------------------------------------------------------------- /docs/src/mkdocstrings/handlers/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base module for handlers. 3 | 4 | This module contains the base classes for implementing collectors, renderers, and the combination of the two: handlers. 5 | 6 | It also provides two methods: 7 | 8 | - `get_handler`, that will cache handlers into the `HANDLERS_CACHE` dictionary. 9 | - `teardown`, that will teardown all the cached handlers, and then clear the cache. 10 | """ 11 | 12 | import copy 13 | import importlib 14 | import re 15 | import textwrap 16 | from abc import ABC, abstractmethod 17 | from pathlib import Path 18 | from typing import Any, Dict, Iterable, List, Optional, Sequence 19 | from xml.etree.ElementTree import Element, tostring 20 | 21 | from jinja2 import Environment, FileSystemLoader 22 | from markdown import Markdown 23 | from markdown.extensions import Extension 24 | from markdown.extensions.codehilite import CodeHiliteExtension 25 | from markdown.treeprocessors import Treeprocessor 26 | from markupsafe import Markup 27 | from pymdownx.highlight import Highlight, HighlightExtension 28 | 29 | from mkdocstrings.loggers import get_template_logger 30 | 31 | CollectorItem = Any 32 | 33 | TEMPLATES_DIR = Path(__file__).parent.parent / "templates" 34 | 35 | 36 | class CollectionError(Exception): 37 | """An exception raised when some collection of data failed.""" 38 | 39 | 40 | class ThemeNotSupported(Exception): 41 | """An exception raised to tell a theme is not supported.""" 42 | 43 | 44 | class Highlighter(Highlight): 45 | """Code highlighter that tries to match the Markdown configuration.""" 46 | 47 | _highlight_config_keys = frozenset( 48 | "use_pygments guess_lang css_class pygments_style noclasses linenums language_prefix".split(), 49 | ) 50 | 51 | def __init__(self, md: Markdown): 52 | """Configure to match a `markdown.Markdown` instance. 53 | 54 | Arguments: 55 | md: The Markdown instance to read configs from. 56 | """ 57 | config = {} 58 | for ext in md.registeredExtensions: 59 | if isinstance(ext, HighlightExtension) and (ext.enabled or not config): 60 | config = ext.getConfigs() 61 | break # This one takes priority, no need to continue looking 62 | if isinstance(ext, CodeHiliteExtension) and not config: 63 | config = ext.getConfigs() 64 | config["language_prefix"] = config["lang_prefix"] 65 | self._css_class = config.pop("css_class", "highlight") 66 | super().__init__(**{k: v for k, v in config.items() if k in self._highlight_config_keys}) 67 | 68 | def highlight( # noqa: W0221 (intentionally different params, we're extending the functionality) 69 | self, 70 | src: str, 71 | language: str = None, 72 | *, 73 | inline: bool = False, 74 | dedent: bool = True, 75 | linenums: Optional[bool] = None, 76 | **kwargs, 77 | ) -> str: 78 | """ 79 | Highlight a code-snippet. 80 | 81 | Arguments: 82 | src: The code to highlight. 83 | language: Explicitly tell what language to use for highlighting. 84 | inline: Whether to highlight as inline. 85 | dedent: Whether to dedent the code before highlighting it or not. 86 | linenums: Whether to add line numbers in the result. 87 | **kwargs: Pass on to `pymdownx.highlight.Highlight.highlight`. 88 | 89 | Returns: 90 | The highlighted code as HTML text, marked safe (not escaped for HTML). 91 | """ 92 | if dedent: 93 | src = textwrap.dedent(src) 94 | 95 | kwargs.setdefault("css_class", self._css_class) 96 | old_linenums = self.linenums 97 | if linenums is not None: 98 | self.linenums = linenums 99 | try: 100 | result = super().highlight(src, language, inline=inline, **kwargs) 101 | finally: 102 | self.linenums = old_linenums 103 | 104 | if inline: 105 | return Markup(f'<code class="highlight language-{language}">{result.text}</code>') 106 | return Markup(result) 107 | 108 | 109 | def do_any(seq: Sequence, attribute: str = None) -> bool: 110 | """ 111 | Check if at least one of the item in the sequence evaluates to true. 112 | 113 | The `any` builtin as a filter for Jinja templates. 114 | 115 | Arguments: 116 | seq: An iterable object. 117 | attribute: The attribute name to use on each object of the iterable. 118 | 119 | Returns: 120 | A boolean telling if any object of the iterable evaluated to True. 121 | """ 122 | if attribute is None: 123 | return any(seq) 124 | return any(_[attribute] for _ in seq) 125 | 126 | 127 | class BaseRenderer(ABC): 128 | """ 129 | The base renderer class. 130 | 131 | Inherit from this class to implement a renderer. 132 | 133 | You will have to implement the `render` method. 134 | You can also override the `update_env` method, to add more filters to the Jinja environment, 135 | making them available in your Jinja templates. 136 | 137 | To define a fallback theme, add a `fallback_theme` class-variable. 138 | To add custom CSS, add an `extra_css` variable or create an 'style.css' file beside the templates. 139 | """ 140 | 141 | fallback_theme: str = "" 142 | extra_css = "" 143 | 144 | def __init__(self, directory: str, theme: str, custom_templates: Optional[str] = None) -> None: 145 | """ 146 | Initialize the object. 147 | 148 | If the given theme is not supported (it does not exist), it will look for a `fallback_theme` attribute 149 | in `self` to use as a fallback theme. 150 | 151 | Arguments: 152 | directory: The name of the directory containing the themes for this renderer. 153 | theme: The name of theme to use. 154 | custom_templates: Directory containing custom templates. 155 | """ 156 | paths = [] 157 | 158 | themes_dir = TEMPLATES_DIR / directory 159 | 160 | paths.append(themes_dir / theme) 161 | 162 | if self.fallback_theme: 163 | paths.append(themes_dir / self.fallback_theme) 164 | 165 | for path in paths: 166 | css_path = path / "style.css" 167 | if css_path.is_file(): 168 | self.extra_css += "\n" + css_path.read_text(encoding="utf-8") 169 | break 170 | 171 | if custom_templates is not None: 172 | paths.insert(0, Path(custom_templates) / directory / theme) 173 | 174 | self.env = Environment( 175 | autoescape=True, 176 | loader=FileSystemLoader(paths), 177 | auto_reload=False, # Editing a template in the middle of a build is not useful. 178 | ) # type: ignore 179 | self.env.filters["any"] = do_any 180 | self.env.globals["log"] = get_template_logger() 181 | 182 | self._headings = [] 183 | self._md = None # To be populated in `update_env`. 184 | 185 | @abstractmethod 186 | def render(self, data: CollectorItem, config: dict) -> str: 187 | """ 188 | Render a template using provided data and configuration options. 189 | 190 | Arguments: 191 | data: The collected data to render. 192 | config: The rendering options. 193 | 194 | Returns: 195 | The rendered template as HTML. 196 | """ # noqa: DAR202 (excess return section) 197 | 198 | def get_anchor(self, data: CollectorItem) -> Optional[str]: 199 | """ 200 | Return the canonical identifier (HTML anchor) for a collected item. 201 | 202 | This must match what the renderer would've actually rendered, 203 | e.g. if rendering the item contains `<h2 id="foo">...` then the return value should be "foo". 204 | 205 | Arguments: 206 | data: The collected data. 207 | 208 | Returns: 209 | The HTML anchor (without '#') as a string, or None if this item doesn't have an anchor. 210 | """ # noqa: DAR202 (excess return section) 211 | 212 | def do_convert_markdown(self, text: str, heading_level: int, html_id: str = "") -> Markup: 213 | """ 214 | Render Markdown text; for use inside templates. 215 | 216 | Arguments: 217 | text: The text to convert. 218 | heading_level: The base heading level to start all Markdown headings from. 219 | html_id: The HTML id of the element that's considered the parent of this element. 220 | 221 | Returns: 222 | An HTML string. 223 | """ 224 | treeprocessors = self._md.treeprocessors 225 | treeprocessors["mkdocstrings_headings"].shift_by = heading_level 226 | treeprocessors["mkdocstrings_ids"].id_prefix = html_id and html_id + "--" 227 | try: 228 | return Markup(self._md.convert(text)) 229 | finally: 230 | treeprocessors["mkdocstrings_headings"].shift_by = 0 231 | treeprocessors["mkdocstrings_ids"].id_prefix = "" 232 | self._md.reset() 233 | 234 | def do_heading( 235 | self, 236 | content: str, 237 | heading_level: int, 238 | *, 239 | hidden: bool = False, 240 | toc_label: Optional[str] = None, 241 | **attributes: str, 242 | ) -> Markup: 243 | """ 244 | Render an HTML heading and register it for the table of contents. For use inside templates. 245 | 246 | Arguments: 247 | content: The HTML within the heading. 248 | heading_level: The level of heading (e.g. 3 -> `h3`). 249 | hidden: If True, only register it for the table of contents, don't render anything. 250 | toc_label: The title to use in the table of contents ('data-toc-label' attribute). 251 | attributes: Any extra HTML attributes of the heading. 252 | 253 | Returns: 254 | An HTML string. 255 | """ 256 | # First, produce the "fake" heading, for ToC only. 257 | el = Element(f"h{heading_level}", attributes) 258 | if toc_label is None: 259 | toc_label = content.unescape() if isinstance(el, Markup) else content 260 | el.set("data-toc-label", toc_label) 261 | self._headings.append(el) 262 | 263 | if hidden: 264 | return Markup('<a id="{0}"></a>').format(attributes["id"]) 265 | 266 | # Now produce the actual HTML to be rendered. The goal is to wrap the HTML content into a heading. 267 | # Start with a heading that has just attributes (no text), and add a placeholder into it. 268 | el = Element(f"h{heading_level}", attributes) 269 | el.append(Element("mkdocstrings-placeholder")) 270 | # Tell the 'toc' extension to make its additions if configured so. 271 | toc = self._md.treeprocessors["toc"] 272 | if toc.use_anchors: 273 | toc.add_anchor(el, attributes["id"]) 274 | if toc.use_permalinks: 275 | toc.add_permalink(el, attributes["id"]) 276 | 277 | # The content we received is HTML, so it can't just be inserted into the tree. We had marked the middle 278 | # of the heading with a placeholder that can never occur (text can't directly contain angle brackets). 279 | # Now this HTML wrapper can be "filled" by replacing the placeholder. 280 | html_with_placeholder = tostring(el, encoding="unicode") 281 | assert ( 282 | html_with_placeholder.count("<mkdocstrings-placeholder />") == 1 283 | ), f"Bug in mkdocstrings: failed to replace in {html_with_placeholder!r}" 284 | html = html_with_placeholder.replace("<mkdocstrings-placeholder />", content) 285 | return Markup(html) 286 | 287 | def get_headings(self) -> Sequence[Element]: 288 | """ 289 | Return and clear the headings gathered so far. 290 | 291 | Returns: 292 | A list of HTML elements. 293 | """ 294 | result = list(self._headings) 295 | self._headings.clear() 296 | return result 297 | 298 | def update_env(self, md: Markdown, config: dict) -> None: # noqa: W0613 (unused argument 'config') 299 | """ 300 | Update the Jinja environment. 301 | 302 | Arguments: 303 | md: The Markdown instance. Useful to add functions able to convert Markdown into the environment filters. 304 | config: Configuration options for `mkdocs` and `mkdocstrings`, read from `mkdocs.yml`. See the source code 305 | of [mkdocstrings.plugin.MkdocstringsPlugin.on_config][] to see what's in this dictionary. 306 | """ 307 | self._md = md 308 | self.env.filters["highlight"] = Highlighter(md).highlight 309 | self.env.filters["convert_markdown"] = self.do_convert_markdown 310 | self.env.filters["heading"] = self.do_heading 311 | 312 | def _update_env(self, md: Markdown, config: dict): 313 | extensions = config["mdx"] + [_MkdocstringsInnerExtension(self._headings)] 314 | 315 | new_md = Markdown(extensions=extensions, extension_configs=config["mdx_configs"]) 316 | # MkDocs adds its own (required) extension that's not part of the config. Propagate it. 317 | if "relpath" in md.treeprocessors: 318 | new_md.treeprocessors.register(md.treeprocessors["relpath"], "relpath", priority=0) 319 | 320 | self.update_env(new_md, config) 321 | 322 | 323 | class BaseCollector(ABC): 324 | """ 325 | The base collector class. 326 | 327 | Inherit from this class to implement a collector. 328 | 329 | You will have to implement the `collect` method. 330 | You can also implement the `teardown` method. 331 | """ 332 | 333 | @abstractmethod 334 | def collect(self, identifier: str, config: dict) -> CollectorItem: 335 | """ 336 | Collect data given an identifier and selection configuration. 337 | 338 | In the implementation, you typically call a subprocess that returns JSON, and load that JSON again into 339 | a Python dictionary for example, though the implementation is completely free. 340 | 341 | Arguments: 342 | identifier: An identifier for which to collect data. For example, in Python, 343 | it would be 'mkdocstrings.handlers' to collect documentation about the handlers module. 344 | It can be anything that you can feed to the tool of your choice. 345 | config: Configuration options for the tool you use to collect data. Typically called "selection" because 346 | these options modify how the objects or documentation are "selected" in the source code. 347 | 348 | Returns: 349 | Anything you want, as long as you can feed it to the renderer's `render` method. 350 | """ # noqa: DAR202 (excess return section) 351 | 352 | def teardown(self) -> None: 353 | """ 354 | Teardown the collector. 355 | 356 | This method should be implemented to, for example, terminate a subprocess 357 | that was started when creating the collector instance. 358 | """ 359 | 360 | 361 | class BaseHandler: 362 | """ 363 | The base handler class. 364 | 365 | Inherit from this class to implement a handler. 366 | 367 | It's usually just a combination of a collector and a renderer, but you can make it as complex as you need. 368 | """ 369 | 370 | def __init__(self, collector: BaseCollector, renderer: BaseRenderer) -> None: 371 | """ 372 | Initialize the object. 373 | 374 | Arguments: 375 | collector: A collector instance. 376 | renderer: A renderer instance. 377 | """ 378 | self.collector = collector 379 | self.renderer = renderer 380 | 381 | 382 | class Handlers: 383 | """ 384 | A collection of handlers. 385 | 386 | Do not instantiate this directly. [The plugin][mkdocstrings.plugin.MkdocstringsPlugin] will keep one instance of 387 | this for the purpose of caching. Use [mkdocstrings.plugin.MkdocstringsPlugin.get_handler][] for convenient access. 388 | """ 389 | 390 | def __init__(self, config: dict) -> None: 391 | """ 392 | Initialize the object. 393 | 394 | Arguments: 395 | config: Configuration options for `mkdocs` and `mkdocstrings`, read from `mkdocs.yml`. See the source code 396 | of [mkdocstrings.plugin.MkdocstringsPlugin.on_config][] to see what's in this dictionary. 397 | """ 398 | self._config = config 399 | self._handlers: Dict[str, BaseHandler] = {} 400 | 401 | def get_anchor(self, identifier: str) -> Optional[str]: 402 | """ 403 | Return the canonical HTML anchor for the identifier, if any of the seen handlers can collect it. 404 | 405 | Arguments: 406 | identifier: The identifier (one that [collect][mkdocstrings.handlers.base.BaseCollector.collect] can accept). 407 | 408 | Returns: 409 | A string - anchor without '#', or None if there isn't any identifier familiar with it. 410 | """ 411 | for handler in self._handlers.values(): 412 | try: 413 | anchor = handler.renderer.get_anchor(handler.collector.collect(identifier, {})) 414 | except CollectionError: 415 | continue 416 | else: 417 | if anchor is not None: 418 | return anchor 419 | return None 420 | 421 | def get_handler_name(self, config: dict) -> str: 422 | """ 423 | Return the handler name defined in an "autodoc" instruction YAML configuration, or the global default handler. 424 | 425 | Arguments: 426 | config: A configuration dictionary, obtained from YAML below the "autodoc" instruction. 427 | 428 | Returns: 429 | The name of the handler to use. 430 | """ 431 | config = self._config["mkdocstrings"] 432 | if "handler" in config: 433 | return config["handler"] 434 | return config["default_handler"] 435 | 436 | def get_handler_config(self, name: str) -> dict: 437 | """ 438 | Return the global configuration of the given handler. 439 | 440 | Arguments: 441 | name: The name of the handler to get the global configuration of. 442 | 443 | Returns: 444 | The global configuration of the given handler. It can be an empty dictionary. 445 | """ 446 | handlers = self._config["mkdocstrings"].get("handlers", {}) 447 | if handlers: 448 | return handlers.get(name, {}) 449 | return {} 450 | 451 | def get_handler(self, name: str, handler_config: Optional[dict] = None) -> BaseHandler: 452 | """ 453 | Get a handler thanks to its name. 454 | 455 | This function dynamically imports a module named "mkdocstrings.handlers.NAME", calls its 456 | `get_handler` method to get an instance of a handler, and caches it in dictionary. 457 | It means that during one run (for each reload when serving, or once when building), 458 | a handler is instantiated only once, and reused for each "autodoc" instruction asking for it. 459 | 460 | Arguments: 461 | name: The name of the handler. Really, it's the name of the Python module holding it. 462 | handler_config: Configuration passed to the handler. 463 | 464 | Returns: 465 | An instance of a subclass of [`BaseHandler`][mkdocstrings.handlers.base.BaseHandler], 466 | as instantiated by the `get_handler` method of the handler's module. 467 | """ 468 | if name not in self._handlers: 469 | if handler_config is None: 470 | handler_config = self.get_handler_config(name) 471 | module = importlib.import_module(f"mkdocstrings.handlers.{name}") 472 | self._handlers[name] = module.get_handler( 473 | self._config["theme_name"], 474 | self._config["mkdocstrings"]["custom_templates"], 475 | **handler_config, 476 | ) # type: ignore 477 | return self._handlers[name] 478 | 479 | @property 480 | def seen_handlers(self) -> Iterable[BaseHandler]: 481 | """ 482 | Get the handlers that were encountered so far throughout the build. 483 | 484 | Returns: 485 | An iterable of instances of [`BaseHandler`][mkdocstrings.handlers.base.BaseHandler] 486 | (usable only to loop through it). 487 | """ 488 | return self._handlers.values() 489 | 490 | def teardown(self) -> None: 491 | """Teardown all cached handlers and clear the cache.""" 492 | for handler in self.seen_handlers: 493 | handler.collector.teardown() 494 | self._handlers.clear() 495 | 496 | 497 | class _IdPrependingTreeprocessor(Treeprocessor): 498 | def __init__(self, md, id_prefix: str): 499 | super().__init__(md) 500 | self.id_prefix = id_prefix 501 | 502 | def run(self, root: Element): 503 | if not self.id_prefix: 504 | return 505 | for el in root.iter(): 506 | id_attr = el.get("id") 507 | if id_attr: 508 | el.set("id", self.id_prefix + id_attr) 509 | 510 | href_attr = el.get("href") 511 | if href_attr and href_attr.startswith("#"): 512 | el.set("href", "#" + self.id_prefix + href_attr[1:]) 513 | 514 | name_attr = el.get("name") 515 | if name_attr: 516 | el.set("name", self.id_prefix + name_attr) 517 | 518 | if el.tag == "label": 519 | for_attr = el.get("for") 520 | if for_attr: 521 | el.set("for", self.id_prefix + for_attr) 522 | 523 | 524 | class _HeadingShiftingTreeprocessor(Treeprocessor): 525 | regex = re.compile(r"([Hh])([1-6])") 526 | 527 | def __init__(self, md: Markdown, shift_by: int): 528 | super().__init__(md) 529 | self.shift_by = shift_by 530 | 531 | def run(self, root: Element): 532 | if not self.shift_by: 533 | return 534 | for el in root.iter(): 535 | match = self.regex.fullmatch(el.tag) 536 | if match: 537 | level = int(match[2]) + self.shift_by 538 | level = max(1, min(level, 6)) 539 | el.tag = f"{match[1]}{level}" 540 | 541 | 542 | class _HeadingReportingTreeprocessor(Treeprocessor): 543 | regex = re.compile(r"[Hh][1-6]") 544 | 545 | def __init__(self, md: Markdown, headings: List[Element]): 546 | super().__init__(md) 547 | self.headings = headings 548 | 549 | def run(self, root: Element): 550 | for el in root.iter(): 551 | if self.regex.fullmatch(el.tag): 552 | el = copy.copy(el) 553 | # 'toc' extension's first pass (which we require to build heading stubs/ids) also edits the HTML. 554 | # Undo the permalink edit so we can pass this heading to the outer pass of the 'toc' extension. 555 | if len(el) > 0 and el[-1].get("class") == self.md.treeprocessors["toc"].permalink_class: 556 | del el[-1] 557 | self.headings.append(el) 558 | 559 | 560 | class _MkdocstringsInnerExtension(Extension): 561 | def __init__(self, headings: List[Element]): 562 | super().__init__() 563 | self.headings = headings 564 | 565 | def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent method's name) 566 | """ 567 | Register the extension. 568 | 569 | Arguments: 570 | md: A `markdown.Markdown` instance. 571 | """ 572 | md.registerExtension(self) 573 | md.treeprocessors.register( 574 | _HeadingShiftingTreeprocessor(md, 0), 575 | "mkdocstrings_headings", 576 | priority=12, 577 | ) 578 | md.treeprocessors.register( 579 | _IdPrependingTreeprocessor(md, ""), 580 | "mkdocstrings_ids", 581 | priority=4, # Right after 'toc' (needed because that extension adds ids to headers). 582 | ) 583 | md.treeprocessors.register( 584 | _HeadingReportingTreeprocessor(md, self.headings), 585 | "mkdocstrings_headings_list", 586 | priority=1, # Close to the end. 587 | ) 588 | -------------------------------------------------------------------------------- /更新日志/version3.md: -------------------------------------------------------------------------------- 1 | ## 更新日志🗓 — v3 2 | 3 | 4 | * v3.9.9 — 2023.6.26 5 | 6 | > 1. download_single_daily和database_update_daily_files新增更新pe_ttm部分 7 | > 2. read_daily新增读取pettm数据 8 | > 3. 修复了pure_moon回测时组数非10组时的画图错误 9 | 10 | 11 | * v3.9.8 — 2023.6.21 12 | 13 | > 1. 修复了read_daily函数中start参数异常的bug 14 | > 2. 给read_h5增加了容错性 15 | > 3. 新增了add_suffix函数,用于给纯数字组成的股票代码添加后缀 16 | > 4. 修复了clip_mad中的偶尔异常 17 | > 5. 修复了to_tradeends在2013年以前异常的bug 18 | > 6. 给pure_fall_frequent在截面整体计算时增加了容错性;并允许select_one_calculate方法返回空值 19 | 20 | 21 | * v3.9.7 — 2023.6.14 22 | 23 | > 1. 将read_daily中交易状态的数据,非正常交易(即值不为1的部分)全部改为nan;将非正常交易日的其他数据改为nan 24 | > 2. 修复了pure_dawn中,使用whole_cross时的bug 25 | > 3. 给clip_mad增加了keep_trend参数,用于将超出3倍mad范围外的异常值替换为均匀分布在3倍~3.5倍区间内的值 26 | > 4. 给pure_moon新增了group_rets_skews和group_rets_skew,用于展示不同分组之间收益的偏度 27 | > 5. 修复了pure_moonnight中comment_yearly方法的结果错误 28 | > 6. 给pure_fall_frequent的from_cross_via_zip装饰器新增了对返回dataframe的支持 29 | 30 | 31 | * v3.9.6 — 2023.6.9 32 | 33 | > 1. 使用read_daily函数读取换手率时,为0的值替换为nan 34 | > 2. 给corr_two_daily新增了daily和method参数,用于指定是否每天计算以及计算方式 35 | > 3. 给cov_two_daily新增了daily参数,用于指定是否每天计算 36 | > 4. 给clip_mad函数新增了replace参数,用于指定将超出临界值的值替换为临界值(否则删除) 37 | > 5. 修复了download_single_daily下载换手率数据中的bug 38 | > 6. 修复了pure_moon中回测绩效因子覆盖率指标的计算错误 39 | 40 | 41 | * v3.9.5 — 2023.5.16 42 | 43 | > 1. 给ClickHouseClient的get_data方法、Questdb的write_via_df方法、get_data_with_tuple方法新增了retry(用以支持pure_fall_frequent的并行) 44 | > 2. 修正了database_update_daily_files中检查历史文件的错误 45 | > 3. 将pure_coldwinter中展示的相关系数统一为spearman相关系数 46 | > 4. 修复了pure_dawn更新历史数据的bug 47 | 48 | 49 | * v3.9.4 — 2023.5.9 50 | 51 | > 1. 优化了read_daily函数中start参数的类型提示 52 | > 2. 删去了一些不必要的加载 53 | > 3. 给pure_moonnight、test_on_index_four_out、test_on_300500、test_on_index_four、pure_fama、follow_tests新增了total_cap参数,表示加权和行业市值中性化时使用总市值,而非流通市值 54 | > 4. 将test_on_index_four_out、test_on_300500、test_on_index_four、pure_fama、follow_tests中的value_weighted或index_member_value_weighted参数默认值修改为1 55 | > 5. 新增了symmetrically_orthogonalize函数,用于给一组因子做对称正交 56 | > 6. 新增了icir_weight函数,用于给一组因子做ICIR加权合成 57 | > 7. 新增了scipy_weight函数,用于给一组因子做优化求解版本的ICIR加权合成 58 | > 8. 新增pure_moon_b、pure_week_b、pure_moon_c、pure_week_c类,用于辅助回测 59 | 60 | 61 | * v3.9.3 — 2023.4.24 62 | 63 | > 1. 给pure_moonnight回测绩效,新增“多头超均收益”和“多头收益占比”两个指标,删去了原有的“中心化后偏度”和“二阶自相关性”指标 64 | > 2. 给test_on_index_four_out、test_on_300500、test_on_index_four新增了value_weighted参数,用来决定指数成分股内的测试是否用流通市值加权 65 | > 3. 给follow_tests新增index_member_value_weighted参数,用来决定指数成分股内的测试是否用流通市值加权 66 | 67 | 68 | * v3.9.2 — 2023.4.24 69 | 70 | > 1. 修正了download_single_daily和database_update_daily_files函数中的bug 71 | > 2. 给make_relative_comments和make_relative_comments_plot新增了all_a参数,表示相对中证全指的超额 72 | > 3. 新增test_on_index_four_out函数,用于输出因子在指数成分股内的多头超额绩效表现到xlsx文件中 73 | 74 | * v3.9.1 — 2023.3.25 75 | 76 | > 1. 给read_market和read_index_single函数新增了questdb_host参数 77 | > 2. 将database_update_barra_files改为从米筐更新,将原database_update_barra_files函数改为database_update_barra_files_dcube 78 | > 3. 给pure_fall_frequent和pure_star新增了questdb_host参数,用于指定questdb数据库的host 79 | > 4. 修复了follow_tests中手续费部分绩效写入excel的bug 80 | > 5. 删去了对依赖库numpy的版本限制 81 | 82 | * v3.9.0 — 2023.3.23 83 | 84 | > 1. 删去了Questdb中的write_via_df_old方法 85 | > 2. 给read_daily函数新增了如下参数 86 | > * vwap:平均成交价 87 | > * total_sharenum:总股数 88 | > * amount:原volumes,成交量 89 | > * total_cap:总市值 90 | > * adjfactor:复权因子 91 | > * stop_up:涨停价 92 | > * stop_down:跌停价 93 | > * zxindustry_dummy_code:中信行业哑变量代码版 94 | > * zxindustry_dummy_name:中信行业哑变量名称版 95 | > * swindustry_dummy:申万行业哑变量 96 | > * hs300_member_weight:沪深300成分股权重 97 | > * zz500_member_weight:中证500成分股权重 98 | > * zz1000_member_weight:中证1000成分股权重 99 | > 3. 修复了ge t_list_std中计算的错误 100 | > 4. 在download_single_day和database_update_daily_files中新增了对上述新数据的支持 101 | > 5. 新增了database_update_index_weight函数来更新函数成分股权重 102 | > 6. 给如下回测相关的类和函数新增了trade_cost_double_side参数,用于设置双边手续费率 103 | > * daily_factor_on_industry 104 | > * group_test_on_industry 105 | > * pure_moonnight 106 | > * pure_newyear 107 | > * test_on_300500 108 | > * test_on_index_four 109 | > 7. 给follow_tests函数新增了trade_cost_double_side_list,用于一次性比较多个不同手续费率下的策略表现 110 | > 8. 给如下回测相关的类和函数新增了opens_average_first_day参数,用于设置买入时以当日成交均价(vwap)买入 111 | > * pure_moonnight 112 | > * follow_tests 113 | > * test_on_300500 114 | > * test_on_index_four 115 | > 9. 对pure_moonnight中的回测,不再向后补全一期开收盘价 116 | > 10. 调整了pure_moonnight换手率的展示方式,改为多头组的月均换手率 117 | > 11. 给pure_moonnight新增了comment_yearly方法,用于逐年产生绩效评价 118 | > 12. 新增pure_moon_a和pure_week_a类,用于辅助回测 119 | > 13. 新增了pure_linprog,用于对打分预测的指标,进行线性规划求解 120 | 121 | * v3.8.6 — 2023.3.17 122 | 123 | > 1. 修正了get_fac_via_corr中riskmetrics方法的权重值 124 | > 2. 将get_fac_via_corr的非daily状态下的返回值index改到了月底 125 | > 3. 新增了boom_one函数,仅计算20天均值 126 | > 4. 修复了show_corrs_with_old指定new=1时,method参数失效的bug 127 | > 5. 将依赖库中numpy版本限制在1.23.5以下 128 | 129 | * v3.8.5 — 2023.3.16 130 | 131 | > 1. 自动识别操作系统,并安装对应库 132 | 133 | * v3.8.4 — 2023.3.16 134 | 135 | > 1. 修复了在python3.8以下版本报错的bug 136 | 137 | * v3.8.3 — 2023.3.16 138 | 139 | > 1. 改变调用加载方式,不初始化也可以使用部分功能 140 | > 1. 引入Dict类型代替dict 141 | * v3.8.2 — 2023.3.15 142 | > 1. 新增了get_list_std_weighted,用来计算多个df的加权标准差 143 | > 2. 调整了get_fac_via_corr、show_corr、pure_moon的spearman相关系数的计算方法 144 | > 3. 修复了FactorReader初始化的bug 145 | > 4. 将FactorReader的update_factor方法改为了可以在外部调用 146 | > 5. 给FactorReader新增了add_token方法 147 | > 6. 调整了make_relative_comments和make_relative_comments_plot的综合指数的走势的计算方法 148 | > 7. 去除了show_covs中的冗余参数 149 | > 8. 给show_corrs_with_old新增了only_new参数,默认仅测试新因子与其余旧因子之间的相关性 150 | > 9. 给pure_snowtrain新增了show_corr方法,用来更好展示因子与风格因子之间的相关性 151 | > 10. 修复了test_on_300500和test_on_index_four方法中分组数量不为10的bug 152 | 153 | * v3.8.1 — 2023.3.8 154 | 155 | > 1. 修复了get_fac_via_corr和get_fac_cross_via_func更新时使用数据错误的问题 156 | > 1. 新增了FactorReader模块,可向云端数据库写入新数据,功能与factor_reader==0.3.3的写入部分一致 157 | * v3.8.0 — 2023.3.1 158 | > 1. 修复了同一host下的questdb多次备份的bug 159 | > 1. 修复了Questdb恢复备份的名称不同的bug 160 | > 1. 修复了pure_fall_frequent被打断继续后,读取备份表格异常的bug 161 | * v3.7.9 — 2023.2.28 162 | > 1. 修复了func_two_daily的history参数为None时,无法计算的bug 163 | > 1. 给pure_dawn的run方法新增了history、daily和whole_cross参数,分别用于指定历史文件、是否每日计算、是否使用横截面全部股票一起计算 164 | > 1. 给pure_dawn新增了for_cross_via_zip的参数,用于处理返回多个series的情形 165 | * v3.7.8 — 2023.2.27 166 | > 1. 给pure_fall_frequent的读取questdb备份表时增加了去除重复行的功能 167 | > 1. 将database_update_minute_data_to_postgresql移至future_version模块中,删去了其默认加载 168 | > 1. 优化了database_update_minute_data_to_clickhouse_and_questdb、database_update_minute_data_to_questdb、database_update_barra_files、database_update_money_flow、database_update_zxindustry_member在无更新数据或重复更新时的警告提示 169 | * v3.7.7 — 2023.2.23 170 | > 1. 修复了Questdb的get_data_with_tuple方法,读取数据异常的bug 171 | > 1. 对pure_fall_frequent的get_daily_factors方法新增了n_jobs参数,用于开启基于mpire的并行加速 172 | > 1. 删去了pure_fall_frequent的get_daily_factors方法中,tqdm_inside的参数,只能在外部添加总进度条 173 | > 1. 新增了mpire依赖库,暂时删去了numpy的依赖库 174 | * v3.7.6 — 2023.2.23 175 | > 1. 修复了pure_fall_frequent打断后重读备份表的bug 176 | > 1. Questdb新增了copy_all_tables和upload_all_copies方法,用于备份和恢复数据库 177 | * v3.7.5 — 2023.2.23 178 | > 1. import pure_ocean_breeze时如遇到错误,增加显示了错误原因 179 | > 1. import pure_ocean_breeze时新增了自动检查更新功能 180 | > 1. Questdb的write_via_df方法新增了tuple_col参数,用于写入数值类型为元组或列表的列 181 | > 1. Questdb新增了get_data_with_tuple方法,用于读取数据类型包含元组或列表的表格 182 | > 1. database_read_primary_factors函数新增了name2参数,用于读取多个parquet文件存储的初级因子 183 | > 1. 删去和警告了pure_fall中冗余的方法 184 | > 1. 修正了pure_fall_frequent中,因备份写入方式变化导致的bug 185 | > 1. 移除了pure_fall_flexible的默认加载,转移至future_version中 186 | > 1. 补充了pure_snowstrain的说明文档 187 | > 1. 新增了依赖库questdb 188 | * v3.7.4 — 2023.2.22 189 | > 1. Questdb的写入方式将原write_via_df改为write_via_df_old,新增了通过questdb.ingress.Sender写入的方式write_via_df,并将所有写入方式都改为了write_via_df 190 | > 1. 修复了使用分钟数据计算因子值时,单日数据重复写入的bug 191 | * v3.7.3 — 2023.2.21 192 | > 1. 删去了do_on_dfs装饰器对get_list_std函数的支持 193 | > 1. 修复了database_update_minute_data_to_clickhouse_and_questdb和database_update_minute_data_to_questdb在端口号为9000的设备上的写入bug,并增加了web_port参数,用于手动指定端口参数 194 | > 1. 给do_on_dfs装饰器支持了对tuple的识别 195 | * v3.7.2 — 2023.2.3 196 | > 1. 修复了Questdb通过csv写入数据只能向本地数据库写入的bug 197 | > 1. 对make_relative_comments和make_relative_comments_plot的进行了提速,并新增了多种股票池混合的方式,可以指定多个参数为1,当且仅当hs300和zz500同时为1时,基准指数为中证800指数000906.SH 198 | > 1. 对daily_factor_on300500删去了zz800参数,新增支持多个参数同时为1,此时为合并股票池 199 | > 1. 修复了remove_unavailable对月频因子报错的bug 200 | > 1. 给test_on_300500和test_on_index_four新增了group_num参数,用于指定分组回测时的分组数量 201 | > 1. 修复了do_on_dfs装饰器中,第一个参数不能为None的bug 202 | * v3.7.1 — 2023.1.22 203 | > 1. 给MetaSQLDriver的get_data增加了尝试10次后再报错的设定,以避免偶然出现的连接失败错误 204 | > 1. 修复了pure_fall_frequent中使用questdb调取数据的命令语句的bug 205 | > 1. 修复了pure_fall_frequent中使用questdb调取数据的数据类型的bug 206 | * v3.7.0 — 2023.1.20 207 | > 1. 修复了Questdb通过csv写入表格时,进程占用文件,导致无法删除的bug 208 | > 1. 给read_market、read_index_single、database_update_minute_data_to_clickhouse_and_questdb、database_update_minute_data_to_questdb增加了自动调整web_port参数值的机制 209 | > 1. 给pure_fall_frequent增加了questdb_web_port参数值,用于指定本台设备questdb的web_port值 210 | > 1. 修复了pure_rollingols中betas属性显示异常的bug 211 | * v3.6.9 — 2023.1.19 212 | > 1. 对drop_duplicates_index函数,增加了保留原来index名字的功能 213 | > 1. 删去了pure_moon回测中的弹窗提示 214 | > 1. 补充了一些依赖库 215 | > 1. 修正了一些函数说明 216 | * v3.6.8 — 2023.1.13 217 | > 1. 修复了初始化存在的bug 218 | * v3.6.7 — 2023.1.13 219 | > 1. 修复了Questdb中关于web_port参数的bug 220 | > 1. 修复了database_update_minute_data_to_questdb的bug 221 | > 222 | * v3.6.6 — 2023.1.10 223 | > 1. 新增了database_update_industry_rets_for_stock函数,用于生成每只股票当天对应的一级行业的收益率 224 | > 1. 给read_daily函数新增了swindustry_ret和zxindustry_ret参数,可以读取每只股票当天对应的一级行业的收益率数据 225 | * v3.6.5 — 2023.1.6 226 | > 1. 给Questdb初始化新增了web_port参数,用于表示控制台的端口号 227 | > 1. 给read_daily函数新增了money参数用于读取每日个股成交额、illiquidity参数用于读取每日个股非流动性 228 | > 1. 新增了database_update_illiquidity函数用于更新每天非流动性数据 229 | > 1. 删去了pure_moon中的select_data_time方法,在set_basic_data函数中新增了基础数据为None时从本地读入的方法 230 | > 1. 优化了pure_moonnight因子值时间超过基础数据时间时,结果的展示方式 231 | > 1. 优化了pure_moonnight的运算逻辑,对回测进行提速,并恢复了time_start和time_end参数的使用,可以为每次回测单独设定回测区间 232 | > 1. 修复了do_on_dfs装饰器在仅作用于一个目标时,参数不生效的bug 233 | * v3.6.4 — 2022.12.29 234 | > 1. 新增了do_on_dfs装饰器,用于将一个作用于单个dataframe的函数,改造为可以分别对多个dataframe运算,dataframe须处于第一个参数的位置,此外如果对每个dataframe,后续的某个参数各不相同,可使用列表依次输入。 235 | > 2. 修复了clip函数的bug 236 | > 3. 新增了judge_factor_by_third函数,用于依据第三个指标的正负,对两个因子进行筛选合成,第三个指标为正则取因子1的值,为负则取因子2的值 237 | > 4. 给pure_fall_frequent的run函数新增了many_days参数,用于对每天取之前的n天的分钟数据来计算因子,当groupby_target指定为`['date','code']`或`['code']`时,则不涉及截面,每只股票各自算自己的因子值,返回值应为float、list、tuple;当groupby_target指定为`[]`时,则取多天的截面数据,返回值应为pd.Series(index为股票代码,values为因子值),或多个这样的pd.Series 238 | > 5. 给pure_fall_frequent新增了drop_table函数,用于删去计算到一半被打断后,不想要的questdb暂存表格 239 | > 6. 新增get_group函数,使用groupby的方法,将一组因子值改为截面上的分组值,此方法相比qcut的方法更加稳健,但速度更慢一些 240 | * v3.6.3 — 2022.12.25 241 | > 1. 修复了database_read_final_factors函数读取周频因子数据的bug 242 | > 1. 修复了merge_many及其他相关函数的bug 243 | > 1. 将corr_two_daily、com_two_daily、func_two_daily的默认并行数改为了1 244 | > 1. 将show_corrs函数不再使用print打印相关系数表格,而是直接返回,此外print_bool参数的函数改为绝对是否将返回值的数值变为百分数 245 | > 1. 修复了show_corrs_with_old时的数字混乱bug 246 | * v3.6.2 — 2022.12.18 247 | > 1. 给read_daily函数增加了vol_daily参数,用于读取使用分钟收益率的标准差计算的每日波动率 248 | > 1. 使read_daily读出的数据不再包含全为空值的行 249 | > 1. 修复了因子标号跳跃导致的show_corrs_with_old函数显示不全的bug 250 | * v3.6.1 — 2022.12.14 251 | > 1. 修复了drop_duplicates_index的bug 252 | > 1. 给pure_moonnight增加了without_breakpoint参数,用于控制iplot画图忽略空格 253 | > 1. 新增了和pure_moon功能一模一样的pure_week,并修复了在同一进程中,不能使用pure_moonnight同时进行月频回测和周频回测的bug 254 | * v3.6.0 — 2022.12.13 255 | > 1. 读取市场指数数据的函数read_market不再限定只能读取中证全指的数据,改为可以通过market_code参数读取任何指数数据 256 | > 1. 修复了read_market函数中使用questdb读取最高价、最低价、开盘价数据的bug,以及不能指定起始时间的bug 257 | > 1. 给read_index_single函数增加了说明 258 | > 1. 向读取h5相关文件函数添加了下线警告 259 | > 1. 给convert_code函数增加了把wind代码转化为米筐代码的功能 260 | > 1. 修复了drop_duplicates_index函数在某些情况下可能出现bug的问题 261 | > 1. 新增了对因子去极值的函数clip,以及三个细分函数clip_mad、clip_three_sigma、clip_percentile 262 | > 1. 修复了pure_moon绩效输出到excel中时,信息比率写入错误的bug 263 | > 1. 给pure_fall_frequent新增了project参数,用于标记每个因子所属于的项目,便于管理 264 | > 1. 修复了pure_fall_frequent打断后重新运行后可能在的bug 265 | > 1. 删去了pure_fall_frequent中关于“共x段”的显示 266 | * v3.5.9 — 2022.11.29 267 | > 1. 修复了读取不复权价格数据时的bug 268 | > 269 | > 2. 新增了将所有因子截面上减去最小值,使其变为非负数的函数all_pos 270 | > 271 | > 3. 新增了对日票或月频因子值剔除st股、停牌股、上市不足60天的函数remove_unavailable 272 | > 273 | > 4. pure_moon类中set_basic_data函数的参数值不再必填 274 | > 275 | > 5. 修复了pure_moon不做行业市值中性化时,factors_out属性的columns异常的问题 276 | > 277 | > 6. 将pure_moon回测的绩效指标的`RankIC均值t值`改为了`RankIC.t` 278 | > 279 | > 7. pure_moon回测新增了6个绩效指标 280 | > > * self.factor_cover:因子覆盖率,为因子的数量与复权开盘价数据的数量的比值 281 | > > * self.pos_neg_rate:因子正值占比,为原始因子值中,为正数的数量占非零值数量的比值 282 | > > * self.factor_cross_skew:因子截面偏度,为原始因子值每个截面的偏度序列的均值,为0则为正态分布,为正则右偏(右边尾巴长),小于零则左偏(左边尾巴长) 283 | > > * self.factor_cross_skew_after_neu:中性化后偏度,对因子进行行业市值中性化之后,平均截面偏度 284 | > > * self.corr_itself:一阶自相关性,原始因子值与其上一期的因子值之间的对应期截面spearman相关系数的均值 285 | > > * self.corr_itself_shift2:二阶自相关性,原始因子值与其上两期的因子值之间的对应期截面spearman相关系数的均值 286 | > 8. 取消了原来的每月换手率变化曲线,新增了每月原始因子值的截面标准差的柱状图 287 | > 8. pure_moon中的回测结果使用cufflinks画图时(即iplot为1时),默认不再显示图例,即ilegend默认值改为了0 288 | > 8. 调整了pure_moon类中set_basic_data函数的形参名称 289 | > 8. 修复了pure_fall_frequent中,暂存至questdb时,表名中带有特殊符号会无法删除表格的bug 290 | > 291 | * v3.5.8 — 2022.11.19 292 | > 1. 给database_read_final_factors和database_save_final_factors新增了`freq`参数,可以指定为'月'或'周',即可存入月频因子数据(滚动20天)和周频因子(滚动5天)数据 293 | > 2. 新增了`计算连续期数`函数,可以用于计算某个指标连续大于或连续小于某个阈值的期数 294 | > 3. 优化了test_on_index_four中,对多头超额净值曲线的显示 295 | * v3.5.7 — 2022.11.17 296 | > 1. 新增了将日频因子进行面板操作以月度化的函数get_fac_via_corr、get_fac_cross_via_func 297 | > 1. 给pure_moon的评价指标新增了RankIC胜率 298 | > 1. 修复了在分钟数据某日出现特殊情况时,会导致pure_fall_frequent在写入questdb时发生错误的bug 299 | * v3.5.6 — 2022.11.14 300 | > 1. 删去了`Homeplace.__slots__`中的minute_data_file、daily_enddate、minute_enddate,以修复新用户初始化后导入失败的bug 301 | * v3.5.5 — 2022.11.13 302 | > 1. 删去了change_index_name函数,可以通过形如`df.index.name='date'`的代码直接指定 303 | > 1. 在write_data模块中,新增了连接米筐失败时,输出失败原因,提供是否等待的选项,并设置了在30秒、60秒、60秒内重连三次的尝试 304 | > 1. 修复了rankic_test_on_industr、follow_tests函数在不指定comments_writer和net_values_writer时报错的bug 305 | > 1. 在pure_ocean_breeze.withs.requires中连接米筐失败时,将给出原因 306 | * v3.5.4 — 2022.11.11 307 | > 1. 给merge_many增加了how参数,可以指定拼接的方式,默认为outer 308 | > 1. 新增zip_many_dfs函数,用于将多个dataframe拼在一起,相同index和columns指向的那个values,连在一起,组成列表 309 | > 1. 新增get_values函数,用于一次性取出一个values为列表的dataframe的所有值,分别设置一个新的dataframe 310 | > 1. 给comment_on_rets_and_nets和comment_on_twins新增参数counts_one_year,用于设置序列的频率 311 | > 1. 新增了boom_fours函数,用于对一个列表的因子值分别算boom_four 312 | * v3.5.3 — 2022.11.6 313 | > 1. 将所有通过is_notebook判断使用何种进度条的函数都改为了通过tqdm.auto模块自动判断 314 | > 1. 将重复更新日频数据database_update_daily_files函数设置为不再报错 315 | > 1. 修复了pure_fall_frequent只更新一天数据时不写入questdb的bug 316 | > 1. 修复了使用pure_fall_frequent进行截面操作时,使用for_cross_via_zip装饰器,只返回一个Series时的bug 317 | > 1. 将pure_ocean_breeze.withs.requires中导入的进度条模块改为tqdm.auto 318 | > 1. 向依赖库中新增了tradetime,删去了SciencePlots、alphalens和cachier 319 | * v3.5.2 — 2022.11.6 320 | > 1. pure_moon和pure_moonnight新增freq参数,可以选择'M',或'W',进行月频测试或周频测试 321 | > 1. 新增frequency_controller类,对回测中不同频率的操作和参数进行控制 322 | > 1. 修复了预存储月度交易状态和st状态的错误 323 | > 1. 优化了pure_moon中部分算法 324 | > 1. pure_ocean_breeze.withs.requires中新增了`import tradetime as tt`用于进行交易日历上的相关操作 325 | * v3.5.1 — 2022.11.5 326 | > 1. 修复了database_update_minute_data_to_clickhouse_and_questdb中的get_price接口变化导致的bug 327 | > 1. 删去了plt.style的设定代码 328 | > 1. 修复了pure_ocean_breeze.withs.requires中导入Iterable的bug 329 | * v3.5.0 — 2022.11.5 330 | > 1. 替换了所有的feather文件读写和存储,改为parquet格式,优化了索引相关的操作 331 | > 1. 优化了pure_moon中关于因子处理的步骤 332 | > 1. legacy_version中收录了v3p4,即3.4.8版本,为最后一个通过feather文件读写的版本 333 | > 1. 新增了feather_to_parquet_all函数,一键将数据库中的所有feather文件都转化为parquet文件 334 | > 1. 删去了依赖包feather,新增了依赖包pyarrow和clickhouse_sqlalchemy 335 | * v3.4.8 — 2022.11.4 336 | > 1. 修复了pure_moon市值加权回测的bug 337 | > 1. 优化了pure_moon的on_paper参数下,学术化评价指标的内容 338 | > 1. 给pure_moonnight增加了 339 | > > * 识别pure_ocean_breeze.state.states中的ON_PAPER参数,用来对环境中所有的回测展示学术化的评价指标 340 | > > * 识别pure_ocean_breeze.state.states中的MOON_START参数,用来对环境中所有的回测的因子规定起点 341 | > > * 识别pure_ocean_breeze.state.states中的MOON_END参数,用来对环境中所有的回测的因子规定终点 342 | > 4. 给pure_ocean_breeze.state.states中增加了ON_PAPER、MOON_START、MOON_END全局参数 343 | > 4. 新增了feather_to_parquet文件,可以将某一文件夹下的所有feather文件转化为parquet文件 344 | > 4. 修复了pure_cloud中的bug 345 | > 4. 修复了pure_ocean_breeze.future_version.in_thoughts模块的导入错误 346 | > 4. 扩展了pure_moonnight的输入因子的类型范围,当前pure_fallmount、pure_snowtrain和dataframe都可以直接输入 347 | * v3.4.7 — 2022.11.4 348 | > 1. 删去了h5py这一依赖项,改为只有在调用read_h5和read_h5_new函数时才会import,并从pure_ocean_breeze.withs.requires中剔除 349 | > 1. 修复了collections中Iterable加载在python3.11版本中的bug 350 | > 1. 修复了pure_fall_frequent.for_cross_via_zip装饰器的bug 351 | > 1. 剔除了对pretty_errors的依赖项 352 | * v3.4.6 — 2022.11.2 353 | > 1. 给read_money_flow增加了whole参数,用于读入当天各类型投资者买卖总量 354 | > 1. 新增了same_columns和same_index函数,取很多dataframe,都只保留共同的columns或index的部分 355 | > 1. 新增了show_x_with_func函数,用于考察两个因子同期截面上的某种关系,并返回一个时间序列 356 | > 1. 新增了show_cov和show_covs函数,考察两个因子或多个因子截面协方差关系 357 | > 1. 将pure_moon修改为,默认不再读入申万一行业哑变量数据,只读入中信一级行业哑变量数据 358 | > 1. 优化了pure_moon回测结果展示图的位置 359 | > 1. 修复了pure_fall_frequent进行截面计算时的bug 360 | > 1. 给pure_fall_frequent新增了for_cross_via_str和for_cross_via_zip装饰器,用于简化截面计算时的对返回值处理的代码 361 | * v3.4.5 — 2022.11.1 362 | > 1. 修复了read_daily中读取不复权最低价时的bug 363 | > 1. 修复了database_update_daily_files更新st股哑变量时的bug 364 | > 1. 修复了pure_moon读取月度状态文件的bug 365 | > 1. 修复了pure_fall_frequent计算单一一天因子值时的bug 366 | > 1. 给pure_ocean_breeze.withs.requires模块中增加了time库 367 | * v3.4.4 — 2022.10.31 368 | > 1. 修复了test_on_index_four使用matplotlib画图不显示的bug 369 | > 1. 新增了择时回测框架pure_star 370 | * v3.4.3 — 2022.10.31 371 | > 1. 给read_index_three增加了国证2000指数的行情数据 372 | > 1. 给make_relative_comments增加了gz2000参数,用于计算相对国证2000指数的超额收益;增加了show_nets参数,用于同时返回多头超额评价指标和超额净值数据 373 | > 1. 给make_relative_comments_plot增加了gz2000参数,用于绘制相当于国证2000指数的超额净值走势图 374 | > 1. 给show_corr增加了show_series参数,将返回值从相关系数的均值改为了相关系数的序列,并取消绘图 375 | > 1. 给pure_moon和pure_moonnight增加了swindustry_dummy和zxindustry_dummy参数,用于自己输入申万一级行业哑变量数据和中信一级行业哑变量数据 376 | > 1. 修复了不同回测结果的IC序列相同的bug 377 | > 1. 给pure_fall_frequent增加了ignore_history_in_questdb的参数,用于被打断后,忽略在questdb中的暂存记录,重新计算;新增了groupby_target参数,用于指定groupby分组计算因子值时,分组的依据(即df.groupby().apply()中groupby里的参数),此改进使得可以进行截面上的构造和计算 378 | > 1. 给test_on_300500增加了gz2000参数,用于测试在国证2000成分股内的多空和多头超额效果 379 | > 1. 恢复了test_on_index_four中的gz2000参数,用于测试国证2000成分股内的效果;优化了多头超额的绩效展示,整合了超额净值曲线图与一张上,使用cufflinks进行显示(可以通过iplot参数关闭cufflinks显示) 380 | * v3.4.2 — 2022.10.29 381 | > 1. 新增了is_notebook函数,可以判断当前环境是否为notebook 382 | > 1. 将除write_data模块以外,其他模块的进度条,都改为可以自动识别环境为notebook,如果是notebook,则自动使用tqdm_notebook的进度条 383 | * v3.4.1 — 2022.10.26 384 | > 1. 给func_two_daily、corr_two_daily、cov_two_daily增加了history参数,用于将计算出的结果记录在本地 385 | > 1. 给show_corrs、show_corrs_with_old函数增加了method参数,可以修改求相关系数的方式 386 | > 1. 暂时删去了test_on_300500的国证2000的参数 387 | > 1. 给test_on_300500和test_on_index_four新增了iplot参数,决定是否使用cufflinks画图 388 | * v3.4.0 — 2022.10.25 389 | > 1. 修复了拼接多个dataframe的函数merge_many中的bug 390 | > 1. 修复了导入process模块时的bug 391 | > 1. 新增了同时测试因子在单个宽基指数成分股上的多空和多头超额表现的函数test_on_3005000 392 | > 1. 新增了同时测试因子在4个宽基指数成分股上的多空和多头超额表现的函数test_on_index_four 393 | * v3.3.9 — 2022.10.24 394 | > 1. 修复了使用clickhouse中get_data时的连接bug 395 | > 1. 修复了拼接多个dataframe的函数merge_many中的bug 396 | > 1. 给func_two_daily、corr_two_daily增加了n_jobs参数,用于决定并行数量 397 | > 1. 新增了滚动求两因子协方差的函数cov_two_daily 398 | > 1. 新增了求目标因子与已发研报因子之间的相关系数的函数show_corrs_with_old 399 | > 1. 修复了pure_fall_frequent计算因子值被打断后,从questdb读取已经计算的因子值时潜在的bug 400 | * v3.3.8 — 2022.10.21 401 | > 1. 对clickhouse、questdb、postgresql数据库的get_data方法增加了只获取np.ndarray的参数 402 | > 1. 给pure_moon增加wind_out属性,用于输出每个时期股票所属分组 403 | > 1. 将pure_fall_frequent中,计算单日因子值的进度条删去 404 | * v3.3.7 — 2022.10.10 405 | > 1. 修复了使用pure_fall(mysql)的分钟数据,更新因子值时的,读取之前因子数据的bug 406 | * v3.3.6 — 2022.10.09 407 | > 1. 新增了剔除北交所因子数据的函数debj 408 | > 1. 新增了对因子做横截面zscore标准化的函数standardlize 409 | > 1. 新增了统计dataframe中有多少(非0)非空数据的函数count_value 410 | > 1. 优化了检测dataframe中是否存在空值的函数detect_nan的计算方法 411 | > 1. 新增了使用若干因子对目标因子正交化的函数de_cross 412 | > 1. 对pure_moon和pure_moonnight新增了使用cufflinks展示回测结果和绘图的参数iplot和ilegend,优化了结果展示的样式 413 | > 1. 修复了pure_fama中,不包含市场因子时的潜在bug 414 | > 1. 对pure_fama中的coefficients属性进行保护 415 | > 1. 新增了pure_rollingols类,用于对若干个因子,对应股票下,进行固定时间窗口的滚动回归 416 | > 1. 调整了一些工具函数的分类,以减少循环引用bug的可能性 417 | * v3.3.5 — 2022.10.07 418 | > 1. 给pure_fall_frequent增加了中途写入questdb防止运算被打断的功能,并可以在打断后通过识别questdb中的数据继续计算,该临时表将在运算全部完成,并成功写入feather文件后删除 419 | > 1. 在Questdb的写入函数write_via_csv的临时csv文件名中,加入了随机数,以避免同时写入文件时的冲突 420 | > 1. 给drop_duplicates_index增加说明,并移至data.tools模块 421 | > 1. 修复了一个讲分钟数据写入postgresql的bug 422 | > 1. 修复了市值中性化函数decap,读取流通市值数据的bug 423 | > 1. 给pure_moon模块的初始化函数参数no_read_indu增加默认参数0,以修复条件双变量排序类pure_newyear的bug 424 | > 1. 修复了pure_fall_frequent中,tqdm_inside不为1时,无法计算第一个交易日的数据的bug 425 | * v3.3.4 — 2022.10.06 426 | > 1. 删去了回测类pure_moon和pure_moonnight中起始日期startdate参数,以避免输入因子值起点不同,会导致缓存失效的bug 427 | > 1. 优化了pure_fama的参数逻辑,在输入add_markert_series参数时,自动将add_market参数指定为1 428 | > 1. 新增了merge_many函数,将多个index为时间,columns位股票代码的dataframe拼接在一起,变成一个长表 429 | > 1. 新增函数func_two_daily,用于对两个index为时间,columns为股票代码的dataframe,每只股票下,各自沿着时间序列,做某个函数操作,最终得到一个index为时间,columns为股票代码的dataframe 430 | > 1. 新增func_two_daily的特例函数,corr_two_daily,求两个因子同一股票滚动窗口下的时序相关系数 431 | * v3.3.3 — 2022.10.01 432 | > 1. 将读取300、500、1000指数的行情read_index_three改为从分钟数据读取 433 | > 1. 给读取市场行情(中证全指)行情read_market增加从questdb读取 434 | > 1. 删除了pure_fall_frequent中使用postgresql中分钟数据计算因子值的选项 435 | > 1. 修复了pure_fall_frequent中使用questdb中的分钟数据计算因子值的部分 436 | * v3.3.2 — 2022.10.01 437 | > 1. 修复了读取隔夜收益率的bug 438 | > 1. 将更新特质收益率数据的起始时间点改为2010年1月1日,并修复了其中的bug 439 | > 1. 给pure_moon和pure_moonnight增加了no_read_indu参数,使回测时不必读入行业哑变量数据,便于调试 440 | > 1. 给pure_moon和pure_moonnight增加了only_cap参数,使回测时只做市值中性化,而不做行业中性化 441 | > 1. 优化了pure_moon和pure_moonnight的参数逻辑,当neutralize和boxcox均为0时,自动开启no_read_indu参数;当no_read_indu和only_cap任一为0时,自动开启另一个参数 442 | * v3.3.1 — 2022.10.01 443 | > 1. 给一键导入库的requires中,增加了import pyfinance.ols as go 444 | > 1. 增加了用于fama三因子与特质收益率相关的类pure_fama,可以计算各期因子收益率、个股各期特质收益率、个股各期因子暴露、超额收益率等内容 445 | > 1. 更新每日数据函数database_update_daily_files增加了更新每日的市盈率pe和市净率pb的数据 446 | > 1. 新增更新每日以20日回归,市场收益率、流通市值分三份、市净率分三份,计算的特质收益率数据 447 | > 1. 一键读取日频数据函数read_daily新增参数ret(日间收益率)、ret_inday(日内收益率)、ret_night(隔夜收益率)、vol(滚动20日波动率)、vol_inday(滚动20日日内收益率波动率)、vol_night(滚动20日隔夜收益率波动率)、swing(振幅)、pb(市净率)、pe(市盈率)、iret(20日回归,市场、流通市值、市净率三因子特质收益率)、ivol(滚动20日,20日回归,市场、流通市值、市净率三因子特质波动率) 448 | > 1. 增加了将dataframe第一列设置为index的函数set_index_first 449 | > 1. 增加了修改index的名字的函数change_index_name 450 | > 1. 丰富了回测类pure_moon和pure_moonnight的评价指标及绘图,新增属性self.ics(ic时间序列)、self.rankics(rankic时间序列)、factor_turnover_rates(每月换手率时间序列)、factor_turnover_rate(平均每月换手率)、group_rets_std(每组组内收益率的标准差)、group_rets_stds(每组组内收益率的标准差的时间序列),新增Rank IC时序图和换手率时序图,美化了评价指标的输出形式 451 | * v3.3.0 — 2022.09.26 452 | > 1. 修复了单独更新questdb内分钟数据的函数database_update_minute_data_to_questdb中的bug 453 | > 1. 新增了依据index去重的函数drop_duplicates_index 454 | > 1. 修复了更新日频数据可能重复的潜在bug 455 | * v3.2.9 — 2022.09.26 456 | > 1. 给pure_helper增加说明 457 | > 1. 修复了以mysql分钟数据更新因子值的类pure_fall的出现重复数据的潜在bug 458 | * v3.2.8 — 2022.09.26 459 | > 1. 修复了用分钟数据计算因子值时,数据重复的潜在bug 460 | > 1. 增加了将因子值改为分组组号的函数to_group 461 | > 1. 增加了根据因子b对因子a进行排序,并在组内使用某种操作的类pure_helper 462 | > 1. 对回测框架的pure_moonnight的输出因子值增加保护 463 | * v3.2.7 — 2022.09.20 464 | > 1. 修复了回测框架pure_moonnight不设置sheetname就无法回测的bug,改为不设置sheeetname就不会写入excel 465 | > 1. 给因子后续必要测试follow_tests增加写入多头在主要指数上的超额净值序列 466 | * v3.2.6 — 2022.09.19 467 | > 1. 通过import pure_ocean_breeze导入库的时候,不再自动导入pure_ocean_breeze.state.states模块内的内容,可通过pure_ocean_breeze.states来调用 468 | > 2. 新增了对因子一键进行必要后续测试的函数follow_tests,包括输出各个分组表现、与常用风格因子相关系数、barra纯净化、在沪深300、中证500、中证1000指数上的多空绩效和多头超额表现、在各个一级行业上的Rank IC值和各个一级行业买n只股票的超额表现 469 | > 3. 在pure_ocean_breeze.state.states模块中,新增COMMENTS_WRITER和NET_VALUES_WRITER参数,用于管理全局所有的pu re_moonnight和follow_tests的绩效记录和净值序列的记录 470 | > 4. 修复了更新日频数据函数database_update_daily_files中,读取旧数据部分的潜在bug 471 | > 5. 删去了pure_ocean_breeze.labor.comment模块中的,输出十组每组绩效表现的函数comments_ten,给pure_moonnight类新增函数pure_moonnight.comments_ten(),用于输入十分组各组绩效表现 472 | > 6. 修复了将因子限定在指数成分股内的函数daily_factor_on300500中,读取指数成分股数据时的潜在bug 473 | > 7. 给讲因子限定在各个一级行业成分股内的函数daily_factor_on_industry,增加了申万一级行业和中信一级行业可选的参数 474 | > 8. 将在各个一级行业上进行分组多空测试的函数group_test_on_swindustry更名为group_test_on_industry,并增加申万一级行业和中信一级行业可选的参数 475 | > 9. 将在各个一级行业上进行Rank IC测试的函数rankic_test_on_swindustry更名为rankic_test_on_industry,并增加申万一级行业和中信一级行业可选的参数 476 | > 10. 修复了在各个一级行业上进行购买n只股票的多头超额测试的函数long_test_on_industry内的中性化bug、读取中信哑变量的bug、股票上市天数的bug、计算收益率序列的bug、读取各个行业指数的bug、中信行业名称bug 477 | > 11. 修复了行业市值中性化函数decap_industry中读取流通市值数据的bug 478 | > 12. 修复了使用clickhouse或questdb的分钟数据更新因子值的类pure_fall_frequent更新因子值时,在每段第一个交易日时的bug 479 | * v3.2.5 — 2022.09.16 480 | > 1. 修复了读取日频数据函数read_daily由于索引名称更改导致的bug 481 | > 1. 修复了缓存机制导致同一内核中,无法转换中信行业和申万行业的bug 482 | > 1. 给用clickhouse的分钟数据计算因子值的类pure_fall_frequent增加了notebook进度条功能,当tqdm_inside指定为-1时,即使用tqdm.tqdm_notebook功能 483 | * v3.2.4 — 2022.09.15 484 | > 1. 改善了以clickhouse和questdb分钟数据计算因子的循环逻辑,将需要计算的时间拆分为多段相邻时间来计算,并补充了起始第一天的计算 485 | > 1. 将保存最终因子值的函数database_save_final_factors增加了去除全空行的功能 486 | * v3.2.3 — 2022.09.13 487 | > 1. 修复了日频数据更新中的bug 488 | > 2. 修复了中信一级行业成分股数据更新的bug 489 | > 3. 将日频数据更新限制时间调整回了17点前 490 | 491 | * v3.2.2 — 2022.09.13 492 | >1. 将日频数据更新的限制时间点由17点前,改为18点前 493 | >1. 修复了调用旧版本时legacy_version的bug 494 | * v3.2.1 — 2022.09.13 495 | >1. 在更新日频数据中加入17点前,仅更新至上一个交易日的限制 496 | >1. 将更新申万一级行业成分股的函数更名为database_update_swindustry_member 497 | >1. 修复了调用旧版本v3p1时的bug 498 | * v3.2.0 — 2022.09.13 499 | >1. 将以mat格式存储的文件,全部转化为feather格式 500 | >1. read_daily函数不再对停牌日的价格进行处理 501 | >1. 删去了更新日频数据的参数,改为只能更新到最新日期 502 | >1. 删去了更新辅助字典的记录,改为从已有的数据中识别上次更新的截止日期 503 | >1. 更新中加入了去重,避免重复更新引起的潜在bug 504 | >1. 在储存最终因子值的函数database_save_final_factors中加入了去重 505 | >1. 增加了中信一级行业成分股数据的函数 506 | >1. 删去了初始化中关于辅助字典的内容,不再创建database_config.db 507 | >1. 删去了初始化中关于日频数据截止日期和分钟数据截止日期的设置要求 508 | >1. 新增在行业上的超额收益测试函数long_test_on_industry,可以选择申万或中信一级行业,默认使用中信 509 | >1. 保留原有风格的行业超额收益测试函数long_test_on_swindustry和long_test_on_zxindustry 510 | >1. 给行业市值中性化函数decap_industry增加中信和申万可选参数,默认使用中信 511 | >1. 简化pure_moon/pure_moonnight回测框架的基础数据处理流程 512 | >1. 给pure_moon/pure_moonnight回测框架的行业中性化部分,增加了中信和申万一级行业可选的参数,默认使用中信 513 | >1. 给pure_moon回测框架增加了设置回测时用到的基础数据的函数set_basic_data 514 | >1. 给pure_moonnight增加了输入基础数据的参数,包括上市天数、是否st、是否正常交易、复权开盘价、复权收盘价、月末流通市值 515 | >1. 删去了pure_moon/pure_moonnight回测框架的by10参数,日频状态月频化时只能使用比较大小的方式 516 | >1. 在旧版本合集legacy_version模块中,收录了最后一个可以处理mat文件的版本3.1.6,使用import pure_ocean_breeze.legacy_version.v3p1 as p即可调用3.1.6版本 517 | * v3.1.6 — 2022.09.03 518 | >1. 修复了用mysql更新因子值时的潜在bug 519 | * v3.1.5 — 2022.09.03 520 | >1. 修复了withs模块的bug 521 | * v3.1.4 — 2022.09.03 522 | >1. 将with模块改为withs模块,避免与关键字冲突 523 | >1. 将更新的源限定为pypi 524 | * v3.1.3 — 2022.09.03 525 | >1. 加入了with模块,可以通过`from pure_ocean_breeze.with.requires import *`加载所有依赖库 526 | >1. 将自动检查新版本改为了需要手动调用check_update()函数来检查 527 | >1. 将行业市值中性化函数decap_industry()改为了可手动指定频率,如果未指定,再自动识别 528 | >1. 修复了限定因子在申万一级行业上的函数daily_factor_on_swindustry()的bug 529 | >1. 修复了使用mysql更新因子值时的表名bug 530 | >1. 修复了使用clickhouse、questdb、postgresql数据库更新因子值时的潜在bug 531 | * v3.1.2 — 2022.08.31 532 | >1. 修复了导入时循环引用的bug 533 | * v3.1.1 — 2022.08.30 534 | >1. 增加了自动检测新版本的功能,在导入库时将自动检测并输出结果 535 | >1. 增加了导入库的时间段统计,可使用函数show_use_times()查看当前设备各个时间段导入次数 536 | >1. 增加了升级库的函数upgrade(),也可以简写为up(),将框架升级为最新版 537 | >1. 将米筐rqdatac改为了非必要加载库 538 | >1. 给read_daily函数增加了一键读入上市天数、流通市值、是否st股、交易状态是否正常的选项 539 | >1. 给读入申万行业指数read_swindustry_prices和中信行业指数read_zxindustry_prices加入起始日期参数 540 | >1. 给获取申万一级行业哑变量的函数get_industry_dummies增加起始日期参数 541 | >1. 增加将一个因子值拆分在各个申万一级行业上的因子值的函数daily_factor_on_swindustry 542 | >1. 增加分别在每个申万一级行业上测试因子分组回测表现的函数group_test_on_swindustry 543 | >1. 增加专门计算因子值在各个申万一级行业上的Rank IC值,并绘制柱状图的函数rankic_test_on_swindustry 544 | >1. 增加对每个申万一级行业成分股,使用某因子挑选出最多头的n值股票,考察其超额收益绩效、每月超额收益、每月每个行业的多头名单的函数long_test_on_swindustry 545 | * v3.1.0 — 2022.08.28 546 | >1. 增加了资金流相关数据,包括一键读入资金流数据的函数read_money_flow和更新数据的函数 547 | >2. 调整了读取因子值和写入因子值函数所属的模块 548 | * v3.0.9 — 2022.08.28 549 | >1. 恢复了read_market函数,将一键读入wind全A指数改为一键读入中证全指000985.SH 550 | * v3.0.8 — 2022.08.25 551 | >1. 修复了更新宽基指数成分股的bug 552 | * v3.0.7 — 2022.08.25 553 | >1. 向数据库模块(pure_ocean_breeze.data.database)中增加了数据库元类,包含不同数据库通用的功能与属性 554 | >1. 向数据库模块中增加使用postgresql与psycopg2引擎的数据库类,包含相关数据库通用的属性与连接方式 555 | >1. 增加postgresql数据库与questdb数据库模块 556 | >1. 统一化mysql数据库的命名 557 | >1. 修复了识别代码为股票与基金代码的函数bug 558 | >1. 增设同时更新clickhouse与questdb分钟数据的函数,以及单独更新questdb和单独更新postgresql的分钟数据的函数 559 | >1. 删去了mysql每只股票/基金一张表的更新部分,仅保留每天一张表 560 | >1. 将mysql的数据存储改为FLOAT,不再乘以100 561 | >1. 以分钟数据计算因子值的部分,增设使用questdb和postgresql计算的选项 562 | * v3.0.6 — 2022.08.21 563 | >1. 修复了使用sql更新因子值的文件读入bug 564 | * v3.0.5 — 2022.08.19 565 | >1. 将旧版本模块与未来版本模块改为了须单独导入 566 | >2. 向本地数据库增设了中信一级行业指数数据 567 | >3. 增加了中信一级行业的米筐代码与行业名称之间的对应字典 568 | * v3.0.4 — 2022.08.18 569 | >1. 增设了旧版本模块(pure_ocean_breeze.legacy_version),目前包括三个旧版本: 570 | >>* v2.6.9(发布时间2022-08-16) 571 | >>* v1.20.7(发布时间2022-07-11) 572 | >>* v1.10.7(发布时间2022-04-04) 573 | >2. 增设了未来版本模块(pure_ocean_breeze.future_version),目前包括两个部分: 574 | >>* pure_ocean_breeze.future_version.half_way(尚未完工的部分) 575 | >>* pure_ocean_breeze.future_version.in_thoughts(仍主要在构思或推敲阶段的部分) 576 | * v3.0.3 — 2022.08.18 577 | >1. 调整了部分模块的名称,使其可以被mkdocstrings识别 578 | >1. 增设了主流宽基指数wind代码与简称的对应字典 579 | >1. 增设专门v2模块,以便在v3版本中调用旧版本代码 580 | * v3.0.2 — 2022.08.18 581 | >1. 剔除常用风格因子的模块(pure_coldwinter/pure_snowtrain)可以增加自定义因子或选择不剔除某些因子 582 | >1. 发送邮件的模块,改为可以不带附件,发送纯文本邮件 583 | * v3.0.1 — 2022.08.17 584 | >1. 删去了回测框架中读入最高价、最低价、成交量和换手率的部分 585 | >1. 将申万行业哑变量的读入时间提前,从而给回测提速60% 586 | * v3.0.0 — 2022.08.17 587 | >1. 上线了[说明文档](https://chen-001.github.io/pure_ocean_breeze/) 588 | >2. 将v2中的pure_ocean_breeze模块拆分为不同功能的几个模块 589 | >>* initialize (初始化) 590 | >>* state (配置&参数) 591 | >>* data (数据) 592 | >>* labor (加工&测试&评价) 593 | >>* mail(通讯) 594 | >3. 修复了主要指数成分股处理的bug,并将其改为日频 595 | >4. 增加了国证2000成分股的哑变量 596 | >5. 删去了初始化中的分钟数据文件路径 --------------------------------------------------------------------------------