quant1x 0.5.2__tar.gz → 0.5.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quant1x-0.5.2 → quant1x-0.5.6}/PKG-INFO +2 -4
- quant1x-0.5.6/quant1x/__init__.py +1 -0
- quant1x-0.5.6/quant1x/base/__init__.py +19 -0
- quant1x-0.5.6/quant1x/base/app.py +36 -0
- quant1x-0.5.6/quant1x/base/base.py +72 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/base/basic.py +62 -62
- quant1x-0.5.6/quant1x/base/cache.py +281 -0
- quant1x-0.5.6/quant1x/base/config.py +52 -0
- quant1x-0.5.6/quant1x/base/device.py +25 -0
- quant1x-0.5.6/quant1x/base/devp.py +27 -0
- quant1x-0.5.6/quant1x/base/exchange.py +340 -0
- quant1x-0.5.6/quant1x/base/file.py +46 -0
- quant1x-0.5.6/quant1x/base/logger.py +21 -0
- quant1x-0.5.6/quant1x/base/market.py +56 -0
- quant1x-0.5.6/quant1x/base/network.py +59 -0
- quant1x-0.5.6/quant1x/base/numerics.py +36 -0
- quant1x-0.5.6/quant1x/base/pattern.py +20 -0
- quant1x-0.5.6/quant1x/base/timestamp.py +170 -0
- quant1x-0.5.6/quant1x/base/version.py +59 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/data/quotes.py +638 -638
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/formula/__init__.py +4 -4
- {quant1x-0.5.2/quant1x/base → quant1x-0.5.6/quant1x/formula}/formula.py +993 -993
- {quant1x-0.5.2/quant1x/base → quant1x-0.5.6/quant1x/formula}/formula_plus.py +191 -191
- {quant1x-0.5.2/quant1x/strategy → quant1x-0.5.6/quant1x/indicators}/__init__.py +4 -3
- {quant1x-0.5.2/quant1x/indicator → quant1x-0.5.6/quant1x/indicators}/ma1x.py +193 -193
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/util/font.py +38 -38
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x.egg-info/PKG-INFO +2 -4
- quant1x-0.5.6/quant1x.egg-info/SOURCES.txt +37 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x.egg-info/requires.txt +0 -3
- {quant1x-0.5.2 → quant1x-0.5.6}/setup.py +3 -2
- quant1x-0.5.2/quant1x/__init__.py +0 -20
- quant1x-0.5.2/quant1x/base/__init__.py +0 -8
- quant1x-0.5.2/quant1x/base/series.py +0 -93
- quant1x-0.5.2/quant1x/data/block.py +0 -179
- quant1x-0.5.2/quant1x/data/futu-basicinfo.py +0 -27
- quant1x-0.5.2/quant1x/data/tdx.py +0 -6
- quant1x-0.5.2/quant1x/data/trade_date.py +0 -55
- quant1x-0.5.2/quant1x/findpeaks/__init__.py +0 -13
- quant1x-0.5.2/quant1x/findpeaks/filters/__init__.py +0 -0
- quant1x-0.5.2/quant1x/findpeaks/filters/frost.py +0 -168
- quant1x-0.5.2/quant1x/findpeaks/filters/kuan.py +0 -139
- quant1x-0.5.2/quant1x/findpeaks/filters/lee.py +0 -147
- quant1x-0.5.2/quant1x/findpeaks/filters/lee_enhanced.py +0 -163
- quant1x-0.5.2/quant1x/findpeaks/filters/mean.py +0 -122
- quant1x-0.5.2/quant1x/findpeaks/filters/median.py +0 -103
- quant1x-0.5.2/quant1x/findpeaks/findpeaks.py +0 -1253
- quant1x-0.5.2/quant1x/findpeaks/interpolate.py +0 -194
- quant1x-0.5.2/quant1x/findpeaks/stats.py +0 -593
- quant1x-0.5.2/quant1x/findpeaks/union_find.py +0 -73
- quant1x-0.5.2/quant1x/indicator/__init__.py +0 -4
- quant1x-0.5.2/quant1x/strategy/bad_base.py +0 -112
- quant1x-0.5.2/quant1x/strategy/linear_regression.py +0 -65
- quant1x-0.5.2/quant1x.egg-info/SOURCES.txt +0 -42
- {quant1x-0.5.2 → quant1x-0.5.6}/LICENSE +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/README.md +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/chart.py +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/data/__init__.py +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x/util/__init__.py +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x.egg-info/dependency_links.txt +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x.egg-info/not-zip-safe +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/quant1x.egg-info/top_level.txt +0 -0
- {quant1x-0.5.2 → quant1x-0.5.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quant1x
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
4
4
|
Summary: Quant1X量化研究
|
|
5
5
|
Home-page: https://gitee.com/quant1x/quant1x
|
|
6
6
|
Author: WangFeng
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
23
24
|
Requires-Dist: setuptools~=78.0.0
|
|
@@ -35,8 +36,6 @@ Requires-Dist: matplotlib~=3.10.5
|
|
|
35
36
|
Requires-Dist: akshare~=1.17.38
|
|
36
37
|
Requires-Dist: findpeaks~=2.7.4
|
|
37
38
|
Requires-Dist: mootdx~=0.11.7
|
|
38
|
-
Requires-Dist: numpy~=2.3.0
|
|
39
|
-
Requires-Dist: pandas~=2.3.0
|
|
40
39
|
Requires-Dist: python-dateutil~=2.9.0.post0
|
|
41
40
|
Requires-Dist: requests==2.32.0
|
|
42
41
|
Requires-Dist: tqdm~=4.67.1
|
|
@@ -46,7 +45,6 @@ Requires-Dist: matplotlib~=3.10.1
|
|
|
46
45
|
Requires-Dist: scipy~=1.16.0
|
|
47
46
|
Requires-Dist: caerus~=0.1.10
|
|
48
47
|
Requires-Dist: joblib~=1.5.1
|
|
49
|
-
Requires-Dist: quant1x-base~=0.6.18
|
|
50
48
|
Dynamic: author
|
|
51
49
|
Dynamic: author-email
|
|
52
50
|
Dynamic: classifier
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
基础工具集
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__author__ = 'WangFeng'
|
|
9
|
+
|
|
10
|
+
from .app import application
|
|
11
|
+
from .device import cpu_num, max_procs
|
|
12
|
+
from .devp import project_path, redirect
|
|
13
|
+
from .file import mkdirs, touch, homedir
|
|
14
|
+
from .logger import logger
|
|
15
|
+
from .network import ip_is_private, ip_is_secure, lan_address
|
|
16
|
+
from .numerics import is_nan, float_round, fix_float
|
|
17
|
+
from .pattern import Singleton
|
|
18
|
+
from .timestamp import TimeRange, FORMAT_DATETIME
|
|
19
|
+
from .timestamp import TradingSession, seconds_to_timestamp
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os.path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from . import file
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def application() -> tuple[str, str, str]:
|
|
9
|
+
"""
|
|
10
|
+
获取应用程序的名字
|
|
11
|
+
:return: 目录, 文件名, 扩展名
|
|
12
|
+
"""
|
|
13
|
+
app = sys.argv[0]
|
|
14
|
+
# print(app)
|
|
15
|
+
# print(os.path.abspath(app))
|
|
16
|
+
abspath = os.path.abspath(app)
|
|
17
|
+
# print(os.path.realpath(app))
|
|
18
|
+
dirname, filename = os.path.split(abspath)
|
|
19
|
+
(filename, ext) = os.path.splitext(filename)
|
|
20
|
+
# print(filename, ext)
|
|
21
|
+
return dirname, filename, ext
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def getuser():
|
|
25
|
+
"""
|
|
26
|
+
获取用户名
|
|
27
|
+
:return:
|
|
28
|
+
"""
|
|
29
|
+
home = file.homedir()
|
|
30
|
+
_, username = os.path.split(home)
|
|
31
|
+
return username
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == '__main__':
|
|
35
|
+
name = application()
|
|
36
|
+
print(name)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
import file
|
|
7
|
+
|
|
8
|
+
def get_quant1x_config_filename() -> str:
|
|
9
|
+
"""
|
|
10
|
+
获取quant1x.yaml文件路径
|
|
11
|
+
:return:
|
|
12
|
+
"""
|
|
13
|
+
# 默认配置文件名
|
|
14
|
+
default_config_filename = 'quant1x.yaml'
|
|
15
|
+
yaml_filename = os.path.join('~', 'runtime', 'etc', default_config_filename)
|
|
16
|
+
yaml_filename = os.path.expanduser(yaml_filename)
|
|
17
|
+
user_home = file.homedir()
|
|
18
|
+
if not os.path.isfile(yaml_filename):
|
|
19
|
+
quant1x_root = os.path.join(user_home, '.quant1x')
|
|
20
|
+
yaml_filename = os.path.join(quant1x_root, default_config_filename)
|
|
21
|
+
yaml_filename = os.path.expanduser(yaml_filename)
|
|
22
|
+
yaml_filename = os.path.expanduser(yaml_filename)
|
|
23
|
+
return yaml_filename
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# 安全加载YAML配置
|
|
27
|
+
def load_config(file_path: str) -> dict:
|
|
28
|
+
"""安全加载YAML配置"""
|
|
29
|
+
try:
|
|
30
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
31
|
+
config = yaml.safe_load(f) or {}
|
|
32
|
+
return config
|
|
33
|
+
except FileNotFoundError:
|
|
34
|
+
raise ValueError(f"配置文件 {file_path} 不存在")
|
|
35
|
+
except yaml.YAMLError as e:
|
|
36
|
+
raise ValueError(f"YAML格式错误: {str(e)}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise ValueError(f"加载配置失败: {str(e)}")
|
|
39
|
+
|
|
40
|
+
class Quant1XConfig:
|
|
41
|
+
_instance = None
|
|
42
|
+
|
|
43
|
+
def __new__(cls):
|
|
44
|
+
if cls._instance is None:
|
|
45
|
+
cls._instance = super().__new__(cls)
|
|
46
|
+
cls._instance._initialize()
|
|
47
|
+
return cls._instance
|
|
48
|
+
|
|
49
|
+
def _initialize(self):
|
|
50
|
+
"""初始化配置"""
|
|
51
|
+
self.__home_path = file.homedir()
|
|
52
|
+
self.__config_filename = get_quant1x_config_filename()
|
|
53
|
+
self.__config = load_config(self.__config_filename)
|
|
54
|
+
|
|
55
|
+
# 初始化路径
|
|
56
|
+
self.__default_main_path = os.path.join(self.__home_path, '.quant1x')
|
|
57
|
+
|
|
58
|
+
self.meta_path = os.path.join(self.__default_main_path, 'meta')
|
|
59
|
+
"""str: 元数据路径"""
|
|
60
|
+
|
|
61
|
+
self.data_path = self.__config.get('basedir', '').strip()
|
|
62
|
+
"""str: 数据目录 """
|
|
63
|
+
|
|
64
|
+
if not self.data_path:
|
|
65
|
+
self.data_path = os.path.join(self.__default_main_path, 'data')
|
|
66
|
+
self.data_path= os.path.expanduser(self.data_path)
|
|
67
|
+
# 数据路径
|
|
68
|
+
self.kline_path = os.path.join(self.data_path, 'day')
|
|
69
|
+
"""str: K线路径 """
|
|
70
|
+
|
|
71
|
+
# 创建配置单例
|
|
72
|
+
config = Quant1XConfig()
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding: UTF-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
最基础的工具
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# A_YI:=100000000;
|
|
9
|
-
# 亿:A_YI;
|
|
10
|
-
# 股价:CLOSE<30;
|
|
11
|
-
# 市值:FINANCE(40)<(2000*A_YI);
|
|
12
|
-
# 涨停比例:IF(FINANCE(3)=2,0.3,IF((FINANCE(3)=4 OR FINANCE(3)=3),0.2,0.1));
|
|
13
|
-
def ZTBL(code: str) -> float:
|
|
14
|
-
"""
|
|
15
|
-
涨停比例, 只支持A股
|
|
16
|
-
|
|
17
|
-
.. note::
|
|
18
|
-
30 68开头的个股涨停比例20%, 其余10%
|
|
19
|
-
|
|
20
|
-
:param code:
|
|
21
|
-
:return:
|
|
22
|
-
"""
|
|
23
|
-
symbol = code[-6:]
|
|
24
|
-
prefix = symbol[0:2]
|
|
25
|
-
if prefix in ['30', '68']:
|
|
26
|
-
return 0.2
|
|
27
|
-
else:
|
|
28
|
-
return 0.1
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def ZTPRICE(close, ztbl) -> float:
|
|
32
|
-
"""
|
|
33
|
-
计算涨停价
|
|
34
|
-
:param close:
|
|
35
|
-
:param ztbl:
|
|
36
|
-
:return:
|
|
37
|
-
"""
|
|
38
|
-
return close * (1 + ztbl)
|
|
39
|
-
|
|
40
|
-
# 停牌:DYNAINFO(8)<=0;
|
|
41
|
-
# ST股:NAMELIKE('ST') OR NAMELIKE('*ST') OR NAMELIKE('S');
|
|
42
|
-
# 真阳线:CLOSE>REF(CLOSE,1);
|
|
43
|
-
# 筹码锁定:BARSLASTCOUNT(LFS>REF(LFS,1))>=2 AND LFS>=30;
|
|
44
|
-
# 浮筹比例:(WINNER(C*1.1)-WINNER(C*0.9))/WINNER(HHV(H,0))*100;
|
|
45
|
-
#
|
|
46
|
-
# JXN1:=5;
|
|
47
|
-
# JXN2:=10;
|
|
48
|
-
# M1:=MA(CLOSE,JXN1);
|
|
49
|
-
# M2:=MA(CLOSE,JXN2);
|
|
50
|
-
# 均线粘合:ABS(M1/M2-1)<0.01;
|
|
51
|
-
#
|
|
52
|
-
# {K线数据}
|
|
53
|
-
# X_HIGH:HIGH;
|
|
54
|
-
# X_LOW:LOW;
|
|
55
|
-
# X_OPEN:OPEN;
|
|
56
|
-
# X_CLOSE:CLOSE;
|
|
57
|
-
# X_VOL:VOL;
|
|
58
|
-
# X_AMOUNT:AMOUNT;
|
|
59
|
-
#
|
|
60
|
-
# {斜率}
|
|
61
|
-
# C_WIDTH:0.009499432279;
|
|
62
|
-
# C_PI:3.141592654;
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
最基础的工具
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# A_YI:=100000000;
|
|
9
|
+
# 亿:A_YI;
|
|
10
|
+
# 股价:CLOSE<30;
|
|
11
|
+
# 市值:FINANCE(40)<(2000*A_YI);
|
|
12
|
+
# 涨停比例:IF(FINANCE(3)=2,0.3,IF((FINANCE(3)=4 OR FINANCE(3)=3),0.2,0.1));
|
|
13
|
+
def ZTBL(code: str) -> float:
|
|
14
|
+
"""
|
|
15
|
+
涨停比例, 只支持A股
|
|
16
|
+
|
|
17
|
+
.. note::
|
|
18
|
+
30 68开头的个股涨停比例20%, 其余10%
|
|
19
|
+
|
|
20
|
+
:param code:
|
|
21
|
+
:return:
|
|
22
|
+
"""
|
|
23
|
+
symbol = code[-6:]
|
|
24
|
+
prefix = symbol[0:2]
|
|
25
|
+
if prefix in ['30', '68']:
|
|
26
|
+
return 0.2
|
|
27
|
+
else:
|
|
28
|
+
return 0.1
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def ZTPRICE(close, ztbl) -> float:
|
|
32
|
+
"""
|
|
33
|
+
计算涨停价
|
|
34
|
+
:param close:
|
|
35
|
+
:param ztbl:
|
|
36
|
+
:return:
|
|
37
|
+
"""
|
|
38
|
+
return close * (1 + ztbl)
|
|
39
|
+
|
|
40
|
+
# 停牌:DYNAINFO(8)<=0;
|
|
41
|
+
# ST股:NAMELIKE('ST') OR NAMELIKE('*ST') OR NAMELIKE('S');
|
|
42
|
+
# 真阳线:CLOSE>REF(CLOSE,1);
|
|
43
|
+
# 筹码锁定:BARSLASTCOUNT(LFS>REF(LFS,1))>=2 AND LFS>=30;
|
|
44
|
+
# 浮筹比例:(WINNER(C*1.1)-WINNER(C*0.9))/WINNER(HHV(H,0))*100;
|
|
45
|
+
#
|
|
46
|
+
# JXN1:=5;
|
|
47
|
+
# JXN2:=10;
|
|
48
|
+
# M1:=MA(CLOSE,JXN1);
|
|
49
|
+
# M2:=MA(CLOSE,JXN2);
|
|
50
|
+
# 均线粘合:ABS(M1/M2-1)<0.01;
|
|
51
|
+
#
|
|
52
|
+
# {K线数据}
|
|
53
|
+
# X_HIGH:HIGH;
|
|
54
|
+
# X_LOW:LOW;
|
|
55
|
+
# X_OPEN:OPEN;
|
|
56
|
+
# X_CLOSE:CLOSE;
|
|
57
|
+
# X_VOL:VOL;
|
|
58
|
+
# X_AMOUNT:AMOUNT;
|
|
59
|
+
#
|
|
60
|
+
# {斜率}
|
|
61
|
+
# C_WIDTH:0.009499432279;
|
|
62
|
+
# C_PI:3.141592654;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
# 缓存的数据
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from dateutil import parser
|
|
10
|
+
from pandas import DataFrame
|
|
11
|
+
|
|
12
|
+
import base
|
|
13
|
+
import exchange
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@lru_cache(maxsize=None)
|
|
17
|
+
def securities() -> pd.DataFrame:
|
|
18
|
+
"""
|
|
19
|
+
证券列表
|
|
20
|
+
"""
|
|
21
|
+
full_path = os.path.join(base.config.meta_path, 'securities.csv')
|
|
22
|
+
if not os.path.isfile(full_path):
|
|
23
|
+
return pd.DataFrame()
|
|
24
|
+
df = pd.read_csv(full_path)
|
|
25
|
+
# 转换为小写
|
|
26
|
+
df.columns = df.columns.str.lower()
|
|
27
|
+
return df[['code', 'name']]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=None)
|
|
31
|
+
def block_list():
|
|
32
|
+
"""
|
|
33
|
+
板块列表
|
|
34
|
+
"""
|
|
35
|
+
df = securities()
|
|
36
|
+
return df[df['code'].astype(str).str.startswith(('sh880', 'sh881'))]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def stock_name(code: str) -> str:
|
|
40
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
41
|
+
df = securities()
|
|
42
|
+
tmp = df[df['code'] == corrected_symbol]
|
|
43
|
+
name = tmp['name'].iloc[0]
|
|
44
|
+
return name
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def klines(code: str) -> pd.DataFrame | None:
|
|
48
|
+
"""
|
|
49
|
+
获取缓存的日线数据
|
|
50
|
+
"""
|
|
51
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
52
|
+
suffix_length = 3 # 修正拼写并明确表示后缀长度
|
|
53
|
+
symbol_directory = os.path.join(base.config.kline_path, corrected_symbol[:-suffix_length]) # 更清晰表达目录用途
|
|
54
|
+
file_extension = '.csv'
|
|
55
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
56
|
+
full_path = os.path.join(symbol_directory, filename)
|
|
57
|
+
|
|
58
|
+
if os.path.isfile(full_path):
|
|
59
|
+
return pd.read_csv(full_path)
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_period_name(period: str = 'D') -> str:
|
|
64
|
+
"""
|
|
65
|
+
根据周期标识返回中文名称
|
|
66
|
+
|
|
67
|
+
Parameters:
|
|
68
|
+
period (str): 周期标识 'W', 'M', 'Q', 'Y'
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: 中文周期名称
|
|
72
|
+
"""
|
|
73
|
+
period_names = {
|
|
74
|
+
'W': '周',
|
|
75
|
+
'M': '月',
|
|
76
|
+
'Q': '季',
|
|
77
|
+
'Y': '年',
|
|
78
|
+
'D': '日'
|
|
79
|
+
}
|
|
80
|
+
period = period.upper()
|
|
81
|
+
return period_names.get(period, period)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def convert_klines_trading(klines, period='D'):
|
|
85
|
+
"""
|
|
86
|
+
基于实际交易日的K线转换函数
|
|
87
|
+
|
|
88
|
+
Parameters:
|
|
89
|
+
klines (pd.DataFrame): 日线数据
|
|
90
|
+
period (str): 目标周期
|
|
91
|
+
'W' - 周线
|
|
92
|
+
'M' - 月线
|
|
93
|
+
'Q' - 季度线
|
|
94
|
+
'Y' - 年线
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
pd.DataFrame: 转换后的K线数据,date字段表示实际交易日
|
|
98
|
+
"""
|
|
99
|
+
if klines.empty:
|
|
100
|
+
return klines.copy()
|
|
101
|
+
|
|
102
|
+
df = klines.copy()
|
|
103
|
+
df['date'] = pd.to_datetime(df['date'])
|
|
104
|
+
df = df.sort_values('date').reset_index(drop=True)
|
|
105
|
+
|
|
106
|
+
# 直接使用简化的周期标识
|
|
107
|
+
period = period.upper()
|
|
108
|
+
if period not in ['W', 'M', 'Q', 'Y']:
|
|
109
|
+
return df
|
|
110
|
+
|
|
111
|
+
# 根据周期分组
|
|
112
|
+
groups = df['date'].dt.to_period(period)
|
|
113
|
+
|
|
114
|
+
# 聚合数据,date字段保留实际的交易日
|
|
115
|
+
result = df.groupby(groups).agg({
|
|
116
|
+
'date': 'last', # 实际最后一个交易日
|
|
117
|
+
'open': 'first',
|
|
118
|
+
'high': 'max',
|
|
119
|
+
'low': 'min',
|
|
120
|
+
'close': 'last',
|
|
121
|
+
'volume': 'sum',
|
|
122
|
+
'amount': 'sum'
|
|
123
|
+
}).reset_index(drop=True)
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# SectorFilename 板块缓存文件名
|
|
129
|
+
def sector_filename(date: str = '') -> str:
|
|
130
|
+
"""
|
|
131
|
+
板块缓存文件名
|
|
132
|
+
"""
|
|
133
|
+
name = 'blocks'
|
|
134
|
+
cache_date = date.strip()
|
|
135
|
+
if len(cache_date) == 0:
|
|
136
|
+
cache_date = exchange.last_trade_date()
|
|
137
|
+
filename = os.path.join(base.config.meta_path, f'{name}.{cache_date}')
|
|
138
|
+
return filename
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@lru_cache(maxsize=None)
|
|
142
|
+
def get_sector_list() -> pd.DataFrame:
|
|
143
|
+
"""
|
|
144
|
+
获取板块列表
|
|
145
|
+
"""
|
|
146
|
+
sfn = sector_filename()
|
|
147
|
+
df = pd.read_csv(sfn)
|
|
148
|
+
df['code'] = 'sh' + df['code'].astype(str)
|
|
149
|
+
return df
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_sector_constituents(code: str) -> list[str]:
|
|
153
|
+
"""
|
|
154
|
+
获取板块成分股列表
|
|
155
|
+
"""
|
|
156
|
+
code = code.strip()
|
|
157
|
+
security_code = exchange.correct_security_code(code)
|
|
158
|
+
df = get_sector_list().copy()
|
|
159
|
+
cs = df[df['code'] == security_code]['ConstituentStocks']
|
|
160
|
+
list = []
|
|
161
|
+
if cs.empty:
|
|
162
|
+
return list
|
|
163
|
+
cs1 = cs.iloc[0]
|
|
164
|
+
ConstituentStocks = json.loads(cs1)
|
|
165
|
+
list = []
|
|
166
|
+
for sc in ConstituentStocks:
|
|
167
|
+
sc = sc.strip()
|
|
168
|
+
sc = exchange.correct_security_code(sc)
|
|
169
|
+
list.append(sc)
|
|
170
|
+
return list
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def date_format(date: str, layout: str = '%Y-%m-%d') -> str:
|
|
174
|
+
dt = parser.parse(date) # 自动识别各种常见日期格式
|
|
175
|
+
return dt.strftime(layout)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@lru_cache(maxsize=None)
|
|
179
|
+
def get_minutes_data(code: str, date: str) -> DataFrame | None:
|
|
180
|
+
"""获取分时数据"""
|
|
181
|
+
code = code.strip()
|
|
182
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
183
|
+
file_extension = '.csv'
|
|
184
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
185
|
+
cache_date = date.strip()
|
|
186
|
+
if len(cache_date) == 0:
|
|
187
|
+
cache_date = exchange.last_trade_date()
|
|
188
|
+
# 获取年份
|
|
189
|
+
cache_date = date_format(cache_date, layout='%Y%m%d')
|
|
190
|
+
year = cache_date[:4]
|
|
191
|
+
base_path = os.path.join(base.config.data_path, 'minutes')
|
|
192
|
+
full_path = os.path.join(base_path, year, cache_date, filename)
|
|
193
|
+
|
|
194
|
+
if os.path.isfile(full_path):
|
|
195
|
+
return pd.read_csv(full_path)
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@lru_cache(maxsize=None)
|
|
200
|
+
def cache_f10(date: str = None) -> DataFrame:
|
|
201
|
+
factor_name = 'f10'
|
|
202
|
+
trade_date = date or exchange.last_trade_date()
|
|
203
|
+
file_extension = exchange.fix_trade_date(trade_date)
|
|
204
|
+
filename = f"{factor_name}.{file_extension}"
|
|
205
|
+
year = trade_date[:4]
|
|
206
|
+
base_path = os.path.join(base.config.data_path, 'flash')
|
|
207
|
+
return pd.read_csv(os.path.join(base_path, year, filename))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_f10(code: str, date: str = None) -> Dict[str, Any]:
|
|
211
|
+
"""
|
|
212
|
+
获取f10数据
|
|
213
|
+
Args:
|
|
214
|
+
code: 证券代码
|
|
215
|
+
date: 日期
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
返回f10数据
|
|
219
|
+
"""
|
|
220
|
+
security_code = exchange.correct_security_code(code)
|
|
221
|
+
df = cache_f10(date)
|
|
222
|
+
result_df = df[df['Code'] == security_code]
|
|
223
|
+
if result_df.empty:
|
|
224
|
+
return {}
|
|
225
|
+
|
|
226
|
+
# 取第一行,转为字典
|
|
227
|
+
return result_df.iloc[0].to_dict()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@lru_cache(maxsize=None)
|
|
231
|
+
def get_tick_transaction(code: str, date: str) -> DataFrame | None:
|
|
232
|
+
"""获取分时"""
|
|
233
|
+
code = code.strip()
|
|
234
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
235
|
+
file_extension = '.csv'
|
|
236
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
237
|
+
cache_date = date.strip()
|
|
238
|
+
if len(cache_date) == 0:
|
|
239
|
+
cache_date = exchange.last_trade_date()
|
|
240
|
+
# 获取年份
|
|
241
|
+
cache_date = date_format(cache_date, layout='%Y%m%d')
|
|
242
|
+
year = cache_date[:4]
|
|
243
|
+
base_path = os.path.join(base.config.data_path, 'trans')
|
|
244
|
+
full_path = os.path.join(base_path, year, cache_date, filename)
|
|
245
|
+
|
|
246
|
+
if os.path.isfile(full_path):
|
|
247
|
+
return pd.read_csv(full_path)
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == '__main__':
|
|
252
|
+
print(base.get_quant1x_config_filename())
|
|
253
|
+
print('data_path', base.config.data_path)
|
|
254
|
+
print('kline_path', base.config.kline_path)
|
|
255
|
+
code = '600600'
|
|
256
|
+
df = klines(code)
|
|
257
|
+
print(df)
|
|
258
|
+
stock_name = stock_name(code)
|
|
259
|
+
print(stock_name)
|
|
260
|
+
security_list = securities()
|
|
261
|
+
print(security_list)
|
|
262
|
+
index_list = block_list()
|
|
263
|
+
print(index_list)
|
|
264
|
+
sfn = sector_filename()
|
|
265
|
+
df = pd.read_csv(sfn)
|
|
266
|
+
print(df)
|
|
267
|
+
print(df['code'].dtype)
|
|
268
|
+
df['code'] = 'sh' + df['code'].astype(str)
|
|
269
|
+
s1 = df[df['code'] == 'sh881478']
|
|
270
|
+
print(s1)
|
|
271
|
+
|
|
272
|
+
l1 = get_sector_constituents('880675')
|
|
273
|
+
print(l1)
|
|
274
|
+
print(type(l1))
|
|
275
|
+
|
|
276
|
+
df2 = get_minutes_data(code, date='2025-06-20')
|
|
277
|
+
print(df2)
|
|
278
|
+
df3 = get_tick_transaction(code, date='2025-06-20')
|
|
279
|
+
print(df3)
|
|
280
|
+
df4 = get_f10(code)
|
|
281
|
+
print(df4)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from icecream import ic
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DictToClass(object):
|
|
10
|
+
"""
|
|
11
|
+
将字典准换为 class 类型
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def to_object(cls, _obj):
|
|
16
|
+
_obj_ = type('new', (object,), _obj)
|
|
17
|
+
[setattr(_obj_, key, cls.to_object(value)) if isinstance(value, dict) else setattr(_obj_, key, value) for
|
|
18
|
+
key, value in _obj.items()]
|
|
19
|
+
return _obj_
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ReadConfigFiles(object):
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""
|
|
25
|
+
获取当前工作路径
|
|
26
|
+
"""
|
|
27
|
+
self.works_path = os.path.dirname(os.path.realpath(__file__))
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def open_file(cls):
|
|
31
|
+
"""
|
|
32
|
+
读取当前工作目录下cfg.yaml文件内容,并返回字典类型
|
|
33
|
+
:return:
|
|
34
|
+
"""
|
|
35
|
+
return yaml.load(
|
|
36
|
+
open(os.path.join(cls().works_path, r'cfg.yaml'), 'r', encoding='utf-8').read(), Loader=yaml.FullLoader
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def cfg(cls, item=None):
|
|
41
|
+
"""
|
|
42
|
+
调用该方法获取需要的配置,item如果为None,返回则是全部配置
|
|
43
|
+
:param item:
|
|
44
|
+
:return:
|
|
45
|
+
"""
|
|
46
|
+
return DictToClass.to_object(cls.open_file().get(item) if item else cls.open_file())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
cfg = ReadConfigFiles.cfg()
|
|
51
|
+
# 测试输出
|
|
52
|
+
ic(cfg.host.group)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
from multiprocessing import cpu_count
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def cpu_num() -> int:
|
|
6
|
+
"""
|
|
7
|
+
获得CPU核数
|
|
8
|
+
:return:
|
|
9
|
+
"""
|
|
10
|
+
return cpu_count()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def max_procs() -> int:
|
|
14
|
+
"""
|
|
15
|
+
最大可使用CPU核数
|
|
16
|
+
默认使用一半的CPU资源
|
|
17
|
+
:return:
|
|
18
|
+
"""
|
|
19
|
+
num = cpu_num()
|
|
20
|
+
return int(num / 2)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == '__main__':
|
|
24
|
+
print(cpu_num())
|
|
25
|
+
print(max_procs())
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def project_path(package: str = "quant1x", filename: str = '') -> str:
|
|
7
|
+
filename = filename.strip()
|
|
8
|
+
if len(filename) == 0:
|
|
9
|
+
# filename = __file__
|
|
10
|
+
raise Exception('filename is empty')
|
|
11
|
+
pos = filename.rfind(package)
|
|
12
|
+
project_path = filename[:pos - 1]
|
|
13
|
+
root_path = os.path.abspath(project_path)
|
|
14
|
+
return root_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def redirect(package: str = "quant1x", filename: str = ''):
|
|
18
|
+
"""
|
|
19
|
+
重定向于当前工程目录
|
|
20
|
+
:return:
|
|
21
|
+
"""
|
|
22
|
+
root_path = project_path(package, filename)
|
|
23
|
+
sys.path.insert(0, root_path)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == '__main__':
|
|
27
|
+
sys.exit(redirect())
|