quant1x 0.7.1__tar.gz → 0.7.2__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.7.1 → quant1x-0.7.2}/PKG-INFO +3 -3
- {quant1x-0.7.1 → quant1x-0.7.2}/README.md +2 -2
- quant1x-0.7.2/quant1x/cache/__init__.py +7 -0
- quant1x-0.7.2/quant1x/cache/f10.py +36 -0
- quant1x-0.7.2/quant1x/cache/market.py +136 -0
- quant1x-0.7.2/quant1x/cache/sector.py +77 -0
- quant1x-0.7.2/quant1x/cache/security.py +56 -0
- quant1x-0.7.2/quant1x/encoding/yaml.py +101 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/indicators/__init__.py +0 -2
- quant1x-0.7.2/quant1x/indicators/cdtd.py +80 -0
- quant1x-0.7.2/quant1x/indicators/f89k.py +162 -0
- quant1x-0.7.2/quant1x/indicators/hjk.py +72 -0
- quant1x-0.7.2/quant1x/indicators/p2fb.py +96 -0
- quant1x-0.7.2/quant1x/level1/__init__.py +3 -0
- quant1x-0.7.2/quant1x/level1/config.py +330 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/level1/protocol.py +29 -12
- {quant1x-0.7.1/quant1x/io → quant1x-0.7.2/quant1x/log}/__init__.py +2 -2
- {quant1x-0.7.1/quant1x/io → quant1x-0.7.2/quant1x/log}/logger.py +28 -4
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/version.py +25 -6
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/PKG-INFO +3 -3
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/SOURCES.txt +13 -5
- quant1x-0.7.2/tests/test_cache.py +48 -0
- quant1x-0.7.1/quant1x/cache.py +0 -363
- quant1x-0.7.1/quant1x/encoding/yaml.py +0 -52
- quant1x-0.7.1/quant1x/level1/__init__.py +0 -7
- quant1x-0.7.1/quant1x/level1/config.py +0 -322
- quant1x-0.7.1/quant1x/market.py +0 -56
- {quant1x-0.7.1 → quant1x-0.7.2}/LICENSE +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/config/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/config/config.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/device.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/devp.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/encoding/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/exchange/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/exchange/code.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/exchange/markets.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/exchange/security.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/formula/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/formula/formula.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/formula/formula_plus.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/indicators/ma1x.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/level1/client.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/level1/security_list.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/net/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/net/connection_pool.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/net/endpoint.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/net/operation_handler.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/network.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/numerics.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/std/__init__.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/std/pattern.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/system.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/timestamp.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/util/__init__.py +0 -0
- {quant1x-0.7.1/quant1x → quant1x-0.7.2/quant1x/util}/chart.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x/util/font.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/dependency_links.txt +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/not-zip-safe +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/requires.txt +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/quant1x.egg-info/top_level.txt +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/setup.cfg +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/setup.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/tests/test_connection_pool.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/tests/test_handshake.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/tests/test_protocol_fixtures.py +0 -0
- {quant1x-0.7.1 → quant1x-0.7.2}/tests/test_protocol_real_network.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quant1x
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Quant1X量化研究
|
|
5
5
|
Home-page: https://gitee.com/quant1x/quant1x
|
|
6
6
|
Author: wangfeng
|
|
@@ -97,7 +97,7 @@ Quant1X 是一个多语言量化交易框架,支持 C++、Go、Rust、Python
|
|
|
97
97
|
| Python | 3.12+ | 3.12.x |
|
|
98
98
|
| Go | 1.25+ | 1.25.x |
|
|
99
99
|
| Rust | 1.88+ | 1.90+ (2021) |
|
|
100
|
-
| C++ | C++20 | GCC
|
|
100
|
+
| C++ | C++20 | GCC 13+/Clang 17+/MSVC 14.3+ |
|
|
101
101
|
|
|
102
102
|
## 🚀 快速开始
|
|
103
103
|
|
|
@@ -106,7 +106,7 @@ Quant1X 是一个多语言量化交易框架,支持 C++、Go、Rust、Python
|
|
|
106
106
|
## 1.1 默认均为64位操作系统
|
|
107
107
|
| python | golang | rust | c++ |
|
|
108
108
|
|:-------|:-------|:-----------|:----------------------------|
|
|
109
|
-
| 3.12.x | 1.25.x | 1.90+/2021 |
|
|
109
|
+
| 3.12.x | 1.25.x | 1.90+/2021 | gcc13+/clang17+/msvc14.3+ |
|
|
110
110
|
|
|
111
111
|
## 1.2 环境安装推荐使用brew
|
|
112
112
|
安装brew时需要注意避免使用root权限
|
|
@@ -36,7 +36,7 @@ Quant1X 是一个多语言量化交易框架,支持 C++、Go、Rust、Python
|
|
|
36
36
|
| Python | 3.12+ | 3.12.x |
|
|
37
37
|
| Go | 1.25+ | 1.25.x |
|
|
38
38
|
| Rust | 1.88+ | 1.90+ (2021) |
|
|
39
|
-
| C++ | C++20 | GCC
|
|
39
|
+
| C++ | C++20 | GCC 13+/Clang 17+/MSVC 14.3+ |
|
|
40
40
|
|
|
41
41
|
## 🚀 快速开始
|
|
42
42
|
|
|
@@ -45,7 +45,7 @@ Quant1X 是一个多语言量化交易框架,支持 C++、Go、Rust、Python
|
|
|
45
45
|
## 1.1 默认均为64位操作系统
|
|
46
46
|
| python | golang | rust | c++ |
|
|
47
47
|
|:-------|:-------|:-----------|:----------------------------|
|
|
48
|
-
| 3.12.x | 1.25.x | 1.90+/2021 |
|
|
48
|
+
| 3.12.x | 1.25.x | 1.90+/2021 | gcc13+/clang17+/msvc14.3+ |
|
|
49
49
|
|
|
50
50
|
## 1.2 环境安装推荐使用brew
|
|
51
51
|
安装brew时需要注意避免使用root权限
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
# 缓存的数据
|
|
3
|
+
|
|
4
|
+
from .security import securities, stock_name
|
|
5
|
+
from .market import klines, get_period_name, convert_klines_trading, get_minutes_data, get_tick_transaction, date_format
|
|
6
|
+
from .sector import block_list, sector_filename, get_sector_list, get_sector_constituents
|
|
7
|
+
from .f10 import cache_f10, get_f10
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
from .. import exchange, config
|
|
8
|
+
|
|
9
|
+
@lru_cache(maxsize=None)
|
|
10
|
+
def cache_f10(date: str = None) -> DataFrame:
|
|
11
|
+
factor_name = 'f10'
|
|
12
|
+
trade_date = date or exchange.last_trade_date()
|
|
13
|
+
file_extension = exchange.fix_trade_date(trade_date)
|
|
14
|
+
filename = f"{factor_name}.{file_extension}"
|
|
15
|
+
year = trade_date[:4]
|
|
16
|
+
base_path = os.path.join(config.quant1x_config.data_path, 'flash')
|
|
17
|
+
return pd.read_csv(os.path.join(base_path, year, filename))
|
|
18
|
+
|
|
19
|
+
def get_f10(code: str, date: str = None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
获取f10数据
|
|
22
|
+
Args:
|
|
23
|
+
code: 证券代码
|
|
24
|
+
date: 日期
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
返回f10数据
|
|
28
|
+
"""
|
|
29
|
+
security_code = exchange.correct_security_code(code)
|
|
30
|
+
df = cache_f10(date)
|
|
31
|
+
result_df = df[df['Code'] == security_code]
|
|
32
|
+
if result_df.empty:
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
# 取第一行,转为字典
|
|
36
|
+
return result_df.iloc[0].to_dict()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import inflection
|
|
6
|
+
from dateutil import parser
|
|
7
|
+
from pandas import DataFrame
|
|
8
|
+
from .. import exchange, config
|
|
9
|
+
|
|
10
|
+
def klines(code: str, freq:str = 'D') -> pd.DataFrame | None:
|
|
11
|
+
"""
|
|
12
|
+
获取缓存的日线数据
|
|
13
|
+
"""
|
|
14
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
15
|
+
suffix_length = 3 # 修正拼写并明确表示后缀长度
|
|
16
|
+
freq_path = 'day' if freq == 'D' else freq
|
|
17
|
+
symbol_directory = os.path.join(config.quant1x_config.data_path, freq_path, corrected_symbol[:-suffix_length]) # 更清晰表达目录用途
|
|
18
|
+
file_extension = '.csv'
|
|
19
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
20
|
+
full_path = os.path.join(symbol_directory, filename)
|
|
21
|
+
|
|
22
|
+
if os.path.isfile(full_path):
|
|
23
|
+
df = pd.read_csv(full_path)
|
|
24
|
+
# 转换所有列名为 snake_case
|
|
25
|
+
df.columns = [inflection.underscore(col) for col in df.columns]
|
|
26
|
+
return df
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def get_period_name(period: str = 'D') -> str:
|
|
30
|
+
"""
|
|
31
|
+
根据周期标识返回中文名称
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
period (str): 周期标识 'W', 'M', 'Q', 'Y'
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: 中文周期名称
|
|
38
|
+
"""
|
|
39
|
+
period_names = {
|
|
40
|
+
'W': '周',
|
|
41
|
+
'M': '月',
|
|
42
|
+
'Q': '季',
|
|
43
|
+
'Y': '年',
|
|
44
|
+
'D': '日'
|
|
45
|
+
}
|
|
46
|
+
period = period.upper()
|
|
47
|
+
return period_names.get(period, period)
|
|
48
|
+
|
|
49
|
+
def convert_klines_trading(klines, period='D'):
|
|
50
|
+
"""
|
|
51
|
+
基于实际交易日的K线转换函数
|
|
52
|
+
|
|
53
|
+
Parameters:
|
|
54
|
+
klines (pd.DataFrame): 日线数据
|
|
55
|
+
period (str): 目标周期
|
|
56
|
+
'W' - 周线
|
|
57
|
+
'M' - 月线
|
|
58
|
+
'Q' - 季度线
|
|
59
|
+
'Y' - 年线
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
pd.DataFrame: 转换后的K线数据,date字段表示实际交易日
|
|
63
|
+
"""
|
|
64
|
+
if klines.empty:
|
|
65
|
+
return klines.copy()
|
|
66
|
+
|
|
67
|
+
df = klines.copy()
|
|
68
|
+
df['date'] = pd.to_datetime(df['date'])
|
|
69
|
+
df = df.sort_values('date').reset_index(drop=True)
|
|
70
|
+
|
|
71
|
+
# 直接使用简化的周期标识
|
|
72
|
+
period = period.upper()
|
|
73
|
+
if period not in ['W', 'M', 'Q', 'Y']:
|
|
74
|
+
return df
|
|
75
|
+
|
|
76
|
+
# 根据周期分组
|
|
77
|
+
groups = df['date'].dt.to_period(period)
|
|
78
|
+
|
|
79
|
+
# 聚合数据,date字段保留实际的交易日
|
|
80
|
+
result = df.groupby(groups).agg({
|
|
81
|
+
'date': 'last', # 实际最后一个交易日
|
|
82
|
+
'open': 'first',
|
|
83
|
+
'high': 'max',
|
|
84
|
+
'low': 'min',
|
|
85
|
+
'close': 'last',
|
|
86
|
+
'volume': 'sum',
|
|
87
|
+
'amount': 'sum'
|
|
88
|
+
}).reset_index(drop=True)
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
def date_format(date: str, layout: str = '%Y-%m-%d') -> str:
|
|
93
|
+
dt = parser.parse(date) # 自动识别各种常见日期格式
|
|
94
|
+
return dt.strftime(layout)
|
|
95
|
+
|
|
96
|
+
@lru_cache(maxsize=None)
|
|
97
|
+
def get_minutes_data(code: str, date: str) -> DataFrame | None:
|
|
98
|
+
"""获取分时数据"""
|
|
99
|
+
code = code.strip()
|
|
100
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
101
|
+
file_extension = '.csv'
|
|
102
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
103
|
+
cache_date = date.strip()
|
|
104
|
+
if len(cache_date) == 0:
|
|
105
|
+
cache_date = exchange.last_trade_date()
|
|
106
|
+
# 获取年份
|
|
107
|
+
cache_date = date_format(cache_date, layout='%Y%m%d')
|
|
108
|
+
year = cache_date[:4]
|
|
109
|
+
base_path = os.path.join(config.quant1x_config.data_path, 'minutes')
|
|
110
|
+
full_path = os.path.join(base_path, year, cache_date, filename)
|
|
111
|
+
|
|
112
|
+
if os.path.isfile(full_path):
|
|
113
|
+
return pd.read_csv(full_path)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
@lru_cache(maxsize=None)
|
|
117
|
+
def get_tick_transaction(code: str, date: str) -> DataFrame | None:
|
|
118
|
+
"""获取分时"""
|
|
119
|
+
code = code.strip()
|
|
120
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
121
|
+
file_extension = '.csv'
|
|
122
|
+
filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
|
|
123
|
+
cache_date = date.strip()
|
|
124
|
+
if len(cache_date) == 0:
|
|
125
|
+
cache_date = exchange.last_trade_date()
|
|
126
|
+
# 获取年份
|
|
127
|
+
cache_date = date_format(cache_date, layout='%Y%m%d')
|
|
128
|
+
year = cache_date[:4]
|
|
129
|
+
base_path = os.path.join(config.quant1x_config.data_path, 'trans')
|
|
130
|
+
full_path = os.path.join(base_path, year, cache_date, filename)
|
|
131
|
+
|
|
132
|
+
if os.path.isfile(full_path):
|
|
133
|
+
df = pd.read_csv(full_path)
|
|
134
|
+
df.columns = [col.lower() for col in df.columns]
|
|
135
|
+
return df
|
|
136
|
+
return None
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from .. import exchange, config
|
|
7
|
+
from .security import securities
|
|
8
|
+
|
|
9
|
+
# SectorFilename 板块缓存文件名
|
|
10
|
+
def sector_filename(date: str = '') -> str:
|
|
11
|
+
"""
|
|
12
|
+
板块缓存文件名
|
|
13
|
+
"""
|
|
14
|
+
name = 'blocks'
|
|
15
|
+
cache_date = date.strip()
|
|
16
|
+
if len(cache_date) == 0:
|
|
17
|
+
cache_date = exchange.last_trade_date()
|
|
18
|
+
filename = os.path.join(config.quant1x_config.meta_path, f'{name}.{cache_date}')
|
|
19
|
+
if not os.path.isfile(filename):
|
|
20
|
+
# fallback to blocks.csv
|
|
21
|
+
filename = os.path.join(config.quant1x_config.meta_path, 'blocks.csv')
|
|
22
|
+
return filename
|
|
23
|
+
|
|
24
|
+
@lru_cache(maxsize=None)
|
|
25
|
+
def get_sector_list() -> pd.DataFrame:
|
|
26
|
+
"""
|
|
27
|
+
获取板块列表
|
|
28
|
+
"""
|
|
29
|
+
sfn = sector_filename()
|
|
30
|
+
try:
|
|
31
|
+
df = pd.read_csv(sfn)
|
|
32
|
+
except FileNotFoundError:
|
|
33
|
+
# fallback: find latest blocks.* file under meta_path
|
|
34
|
+
meta_dir = os.path.dirname(sfn)
|
|
35
|
+
prefix = os.path.join(meta_dir, 'blocks.')
|
|
36
|
+
candidates = [f for f in os.listdir(meta_dir) if f.startswith('blocks.')]
|
|
37
|
+
if not candidates:
|
|
38
|
+
raise
|
|
39
|
+
# pick the latest by modification time
|
|
40
|
+
candidates_full = [os.path.join(meta_dir, f) for f in candidates]
|
|
41
|
+
candidates_full.sort(key=lambda p: os.path.getmtime(p), reverse=True)
|
|
42
|
+
sfn2 = candidates_full[0]
|
|
43
|
+
df = pd.read_csv(sfn2)
|
|
44
|
+
# 补全sh前缀
|
|
45
|
+
s = df['code'].astype(str)
|
|
46
|
+
df['code'] = s.where(s.str.startswith('sh'), 'sh' + s)
|
|
47
|
+
return df
|
|
48
|
+
|
|
49
|
+
@lru_cache(maxsize=None)
|
|
50
|
+
def block_list():
|
|
51
|
+
"""
|
|
52
|
+
板块列表
|
|
53
|
+
"""
|
|
54
|
+
df = securities()
|
|
55
|
+
if df.empty or 'code' not in df.columns:
|
|
56
|
+
return pd.DataFrame(columns=df.columns if not df.empty else ['code', 'name'])
|
|
57
|
+
return df[df['code'].astype(str).str.startswith(('sh880', 'sh881'))]
|
|
58
|
+
|
|
59
|
+
def get_sector_constituents(code: str) -> list[str]:
|
|
60
|
+
"""
|
|
61
|
+
获取板块成分股列表
|
|
62
|
+
"""
|
|
63
|
+
code = code.strip()
|
|
64
|
+
security_code = exchange.correct_security_code(code)
|
|
65
|
+
df = get_sector_list().copy()
|
|
66
|
+
cs = df[df['code'] == security_code]['ConstituentStocks']
|
|
67
|
+
list = []
|
|
68
|
+
if cs.empty:
|
|
69
|
+
return list
|
|
70
|
+
cs1 = cs.iloc[0]
|
|
71
|
+
ConstituentStocks = json.loads(cs1)
|
|
72
|
+
list = []
|
|
73
|
+
for sc in ConstituentStocks:
|
|
74
|
+
sc = sc.strip()
|
|
75
|
+
sc = exchange.correct_security_code(sc)
|
|
76
|
+
list.append(sc)
|
|
77
|
+
return list
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from .. import exchange, config
|
|
6
|
+
|
|
7
|
+
@lru_cache(maxsize=None)
|
|
8
|
+
def securities() -> pd.DataFrame:
|
|
9
|
+
"""
|
|
10
|
+
证券列表
|
|
11
|
+
"""
|
|
12
|
+
full_path = os.path.join(config.quant1x_config.meta_path, 'securities.csv')
|
|
13
|
+
if not os.path.isfile(full_path):
|
|
14
|
+
return pd.DataFrame(columns=['code', 'name'])
|
|
15
|
+
# with open(full_path, 'rb') as f:
|
|
16
|
+
# f.seek(87287 - 13)
|
|
17
|
+
# print('Bytes around 85710:', f.read(19).hex(' '))
|
|
18
|
+
df = pd.read_csv(full_path)
|
|
19
|
+
# 转换为小写
|
|
20
|
+
df.columns = df.columns.str.lower()
|
|
21
|
+
# 兼容多种列名:优先匹配 'code' 和 'name',否则尝试常见替代列名,最终回退到前两列
|
|
22
|
+
cols = list(df.columns)
|
|
23
|
+
code_candidates = ['code', 'symbol', 'securitycode', 'security_code', 'sec_code', 'sid']
|
|
24
|
+
name_candidates = ['name', 'sec_name', 'security_name', 'secname', 'stock_name']
|
|
25
|
+
|
|
26
|
+
def find_first(candidates, cols):
|
|
27
|
+
for c in candidates:
|
|
28
|
+
if c in cols:
|
|
29
|
+
return c
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
code_col = find_first(code_candidates, cols) or (cols[0] if cols else None)
|
|
33
|
+
name_col = find_first(name_candidates, cols) or (cols[1] if len(cols) > 1 else (cols[0] if cols else None))
|
|
34
|
+
|
|
35
|
+
if code_col is None or name_col is None:
|
|
36
|
+
return pd.DataFrame(columns=['code', 'name'])
|
|
37
|
+
|
|
38
|
+
# 返回时保持列名为标准名称 'code' 和 'name',以便后续代码不变
|
|
39
|
+
out = df[[code_col, name_col]].copy()
|
|
40
|
+
out.columns = ['code', 'name']
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
def stock_name(code: str) -> str:
|
|
44
|
+
corrected_symbol = exchange.correct_security_code(code)
|
|
45
|
+
df = securities()
|
|
46
|
+
if df.empty:
|
|
47
|
+
return ""
|
|
48
|
+
# ensure types align
|
|
49
|
+
try:
|
|
50
|
+
tmp = df[df['code'].astype(str) == str(corrected_symbol)]
|
|
51
|
+
except Exception:
|
|
52
|
+
tmp = df[df['code'] == corrected_symbol]
|
|
53
|
+
if tmp.empty:
|
|
54
|
+
return ""
|
|
55
|
+
name = tmp['name'].iloc[0]
|
|
56
|
+
return name
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import yaml
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from yaml import CSafeLoader as Loader, CSafeDumper as Dumper
|
|
9
|
+
except ImportError:
|
|
10
|
+
from yaml import SafeLoader as Loader, SafeDumper as Dumper
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def to_namespace(data):
|
|
14
|
+
"""
|
|
15
|
+
递归将字典转换为 SimpleNamespace,支持属性访问
|
|
16
|
+
"""
|
|
17
|
+
if isinstance(data, dict):
|
|
18
|
+
return SimpleNamespace(**{k: to_namespace(v) for k, v in data.items()})
|
|
19
|
+
elif isinstance(data, list):
|
|
20
|
+
return [to_namespace(i) for i in data]
|
|
21
|
+
return data
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load(path, to_object=False):
|
|
25
|
+
"""
|
|
26
|
+
读取YAML文件
|
|
27
|
+
:param path: 文件路径
|
|
28
|
+
:param to_object: 是否转换为对象(SimpleNamespace)
|
|
29
|
+
:return: 字典或对象
|
|
30
|
+
"""
|
|
31
|
+
if not os.path.exists(path):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
35
|
+
data = yaml.load(f, Loader=Loader)
|
|
36
|
+
|
|
37
|
+
if to_object:
|
|
38
|
+
return to_namespace(data)
|
|
39
|
+
return data
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dump(data, path):
|
|
43
|
+
"""
|
|
44
|
+
保存为YAML文件
|
|
45
|
+
:param data: 数据
|
|
46
|
+
:param path: 文件路径
|
|
47
|
+
"""
|
|
48
|
+
# 注意:SimpleNamespace 不能直接被 yaml dump,如果需要保存对象,需先转回 dict
|
|
49
|
+
# 这里假设 data 是 dict, list 或基础类型
|
|
50
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
51
|
+
yaml.dump(data, f, Dumper=Dumper, allow_unicode=True)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ReadConfigFiles(object):
|
|
55
|
+
"""
|
|
56
|
+
带缓存的配置文件读取类
|
|
57
|
+
"""
|
|
58
|
+
_caches = {} # path -> {'mtime': float, 'data': dict}
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def cfg(cls, path, item=None):
|
|
62
|
+
"""
|
|
63
|
+
调用该方法获取需要的配置,带缓存机制
|
|
64
|
+
:param path: 配置文件路径
|
|
65
|
+
:param item: 配置项名称
|
|
66
|
+
:return: SimpleNamespace 对象或具体值
|
|
67
|
+
"""
|
|
68
|
+
if not path or not os.path.exists(path):
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# 检查文件修改时间,如果有变化则重新加载
|
|
72
|
+
try:
|
|
73
|
+
mtime = os.path.getmtime(path)
|
|
74
|
+
cache = cls._caches.get(path)
|
|
75
|
+
|
|
76
|
+
if cache is None or mtime > cache['mtime']:
|
|
77
|
+
data = load(path)
|
|
78
|
+
cls._caches[path] = {'mtime': mtime, 'data': data}
|
|
79
|
+
else:
|
|
80
|
+
data = cache['data']
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
if not data:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
if item:
|
|
88
|
+
val = data.get(item)
|
|
89
|
+
return to_namespace(val) if isinstance(val, (dict, list)) else val
|
|
90
|
+
|
|
91
|
+
return to_namespace(data)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
try:
|
|
96
|
+
from icecream import ic
|
|
97
|
+
# 示例:需要传入具体路径
|
|
98
|
+
# cfg = ReadConfigFiles.cfg('path/to/config.yaml')
|
|
99
|
+
pass
|
|
100
|
+
except ImportError:
|
|
101
|
+
pass
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
import pandas
|
|
4
|
+
|
|
5
|
+
from quant1x.formula.formula import *
|
|
6
|
+
|
|
7
|
+
np.seterr(divide='ignore', invalid='ignore')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cdtd(data: pandas.DataFrame):
|
|
11
|
+
"""
|
|
12
|
+
抄底逃顶
|
|
13
|
+
:param data:
|
|
14
|
+
:return:
|
|
15
|
+
"""
|
|
16
|
+
df = data.copy()
|
|
17
|
+
OPEN = df['open']
|
|
18
|
+
CLOSE = df['close']
|
|
19
|
+
HIGH = df['high']
|
|
20
|
+
LOW = df['low']
|
|
21
|
+
VOL = df['volume']
|
|
22
|
+
AMOUNT = df['amount']
|
|
23
|
+
|
|
24
|
+
# N1:=3;
|
|
25
|
+
N1 = 3
|
|
26
|
+
# N2:=9;
|
|
27
|
+
N2 = 9
|
|
28
|
+
# N3:=27;
|
|
29
|
+
N3 = 27
|
|
30
|
+
# N4:=5;
|
|
31
|
+
N4 = 5
|
|
32
|
+
# HHVLLV3:=HHV(HIGH,N3)-LLV(LOW,N3);
|
|
33
|
+
HHVLLV3 = HHV(HIGH, N3) - LLV(LOW, N3)
|
|
34
|
+
# df['cdtd.h3'] = HHV(HIGH, N3)
|
|
35
|
+
# df['cdtd.l3'] = LLV(LOW, N3)
|
|
36
|
+
# df['cdtd.hl3'] = HHVLLV3
|
|
37
|
+
# CLLV3:=CLOSE-LLV(LOW,N3);
|
|
38
|
+
CLLV3 = CLOSE - LLV(LOW, N3)
|
|
39
|
+
# RSV1:=(CLOSE-LLV(LOW,N2))/(HHV(HIGH,N2)-LLV(LOW,N2))*100;
|
|
40
|
+
RSV1 = (CLOSE - LLV(LOW, N2)) / (HHV(HIGH, N2) - LLV(LOW, N2)) * 100
|
|
41
|
+
# RSV2:=CLLV3/HHVLLV3*100;
|
|
42
|
+
RSV2 = CLLV3 / HHVLLV3 * 100
|
|
43
|
+
# WEN:=N1*SMA(RSV2,N4,1)-2*SMA(SMA(RSV2,N4,1),N1,1);
|
|
44
|
+
WEN = N1 * SMA(RSV2, N4, 1) - 2 * SMA(SMA(RSV2, N4, 1), N1, 1)
|
|
45
|
+
df['cdtd.wen'] = WEN
|
|
46
|
+
# J1:=SMA(RSV1,N1,1);
|
|
47
|
+
J1 = SMA(RSV1, N1, 1)
|
|
48
|
+
# J2:=SMA(J1,N1,1);
|
|
49
|
+
J2 = SMA(J1, N1, 1)
|
|
50
|
+
# W1:=SMA(RSV2,N1,1);
|
|
51
|
+
W1 = SMA(RSV2, N1, 1)
|
|
52
|
+
# W2:=SMA(W1,N1,1);
|
|
53
|
+
W2 = SMA(W1, N1, 1)
|
|
54
|
+
# 强弱界线:49,DOTLINE,LINETHICK1,COLOR9966CC;
|
|
55
|
+
# 顶:100, DOTLINE,COLORCCFF00;
|
|
56
|
+
# 底:0, DOTLINE, COLORRED;
|
|
57
|
+
# 趋势线:WEN,LINETHICK1,COLORFF84FF;
|
|
58
|
+
# 卖出:=CROSS(J2,J1) AND J2>85;
|
|
59
|
+
S1 = CROSS(J2, J1)
|
|
60
|
+
S2 = J2 > 85
|
|
61
|
+
S = S1 & S2
|
|
62
|
+
df['cdtd.S1'] = S
|
|
63
|
+
# 卖点预警线:90,DOTLINE,LINETHICK1,COLORBLUE;
|
|
64
|
+
# DRAWICON(卖出, 70, 2);
|
|
65
|
+
# 买入:=趋势线<REF(趋势线,1) AND 趋势线<=5;
|
|
66
|
+
B1 = WEN < REF(WEN, 1)
|
|
67
|
+
B2 = WEN <= 5
|
|
68
|
+
B = B1 & B2
|
|
69
|
+
df['cdtd.B'] = B
|
|
70
|
+
# 买点预警线:10,DOTLINE,LINETHICK1,COLORWHITE;
|
|
71
|
+
# DRAWICON(买入, 30, 1);
|
|
72
|
+
# G1:=W1{,LINETHICK2,COLORWHITE};
|
|
73
|
+
# G2:=W2{,LINETHICK2,COLORCYAN};
|
|
74
|
+
# STICKLINE(趋势线>=85,100,趋势线,5,1),COLORGREEN;
|
|
75
|
+
# STICKLINE(趋势线<=5,0,趋势线,5,1),COLORYELLOW;
|
|
76
|
+
df['cdtd.S2'] = B2
|
|
77
|
+
# STICKLINE(COUNT(趋势线<REF(趋势线,1) AND 趋势线<=5,2)=2,0,20,8,0),COLORRED;
|
|
78
|
+
df['cdtd.S3'] = COUNT(B1 & B2, 2) == 2
|
|
79
|
+
# STICKLINE(CROSS(J2,J1) AND J2>=85,100,80,8,0),COLORGREEN;
|
|
80
|
+
return df
|